Lesson 01: A Basic Helium App
- Jacques Marais
- Former user (Deleted)
- Former user (Deleted)
As a System Admin I want to see a welcome page with my details when I log into the app.
Lesson Outcomes
By the end of this lesson you should:
- Have an understanding of the folder structure for Helium apps
- Have a general understanding of the design of a Helium app
- Be able to create a basic Helium app
- Be able to compile and deploy a basic app
Adding an Object
As a first step we will add a file named SystemAdmin.mez
to the model folder. For an overview of how the model folder fits into a DSL project, along with other folders that will be created in later sections of this tutorial, see the additional documentation provided here. Note the use of the mez
extension in our SystemAdmin.mez
model file. This is used for all Helium script files. This includes files containing data model objects and units. Note that while it is required that model files be placed under the model folder, there is no limitation to the use of subfolders within the model folder. Using subfolders to logically organise your model files is recommended practice.
We can now add the following code to our newly created file to define the object:
persistent object SystemAdmin { string firstName; string lastName; string mobileNumber; string emailAddress; }
Although it is recommended, it is not required to name the file and object within the file similarly. Note the optional use of the persistent
keyword in the object definition. This implies that Helium will create a database table for the object. All instances of the object that we save will be stored as a record in this table. Alternatively this can be omitted in which case all instance of the object will only be kept in memory.
In the example above, all attributes have type string
. This is a basic data type in Helium and represents a string of characters. For a reference to the different basic data types please refer to the Quick Reference.
Use subfolders to logically organise the model files in your app.
Maintain consistency between your model file names and model object names.
Adding a User Role
The object that we have created will be used to represent details of a specific user role namely the "
System
Admin"
role. In order for Helium to recognize this we need to use the @Role
annotation and also specify a name for the role. Note that there is no restriction for the role name to be similar to the object name although it is recommended practice. Also take note that the role name, as specified using the @Role
annotation, will be displayed to users on the frontend. It is therefore recommended to avoid camel case, underscores and other programming notation for role names. See the code example below.
@Role("System Admin") persistent object SystemAdmin { string firstName; string lastName; string mobileNumber; string emailAddress; }
Note that adding the @Role
annotation to an object implies that it will be persistent whether the persistent
keyword is present or not.
Keep roles names and object names similar.
Avoid programming notation for role names as they will be displayed to users on the frontend.
Objects with the @Role
annotation are implied to be persistent.
Adding a View
Next we need to add a view for the "System Admin
" role that was added in the previous step. To do this create a view file named SystemAdminHome.vxml
inside the web-app/views
folder. Once again, there is no restriction on the use of subfolders within the views folder. View file names have to be unique. Add the following to the view file:
<?xml version="1.0" encoding="UTF-8"?> <ui xmlns="http://uiprogram.mezzanine.com/View" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://uiprogram.mezzanine.com/View View.xsd"> <view label="view_heading.system_admin_home"> </view> </ui>
Use subfolders to logically organise the views in your app.
View file names must be unique.
Creating and Using a Language File
Note that the view file added in the previous step contains the following:
<view label="view_heading.system_admin_home">
This indicates that the view title references an entry in a properties file with the key view_heading.system_admin_home. This properties file needs to be created under the lang folder and an entry needs to be added for this key. For now we will only add a default properties file with name en.lang. Multiple properties files can, however, be added here where each one represents a different language. Helium will then use the lang files, the property key and the locale of the current user to retrieve the values that need to be displayed on the frontend. The example below shows our key and value for the view title as found in the default properties file named en.lang.
view_heading.system_admin_home = System Admin Home
Keep properties files organized and decide on a naming scheme for keys in these files.
The translation entries declared in the DSL app source code can be overridden at runtime, for more information please see Helium Translation Overrides
Adding a Menu Item
The menu item defines an entry point to the view, specifically for the "System Admin"
role in this case. One can also specify the index of the menu item in the menu starting at index 0 as well as a custom icon. Icons are located in the images folder and are referenced in the menu item using the file name without extension. A lang file entry for the menu item name also needs to be added. It is also possible to have more that one menu item per view which represents menu items for the same view but different roles. With a menu item added our view is now as follows:
<?xml version="1.0" encoding="UTF-8"?> <ui xmlns="http://uiprogram.mezzanine.com/View" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://uiprogram.mezzanine.com/View View.xsd"> <view label="view_heading.system_admin_home"> <!-- Menu item for System Admin role --> <menuitem label="menu_item.system_admin_home" icon="UserProfile" order="0"> <userRole>System Admin</userRole> </menuitem> </view> </ui>
Note: Add a profile icon under web-app/images called UserProfile. (Look in given source code for images like this.)
The following entry, representing our menu item label, is also added to our en.lang
properties file:
menu_item.system_admin_home = System Admin Home
The menu item shown in this lesson shows the most basic and common usage of a menu item. For an advanced example of how dynamic values can be used to represent the user roles, icon, label and preferred order of a menu item, please consult the following reference documentation: Advanced Menu item Usage
The recommended format for icons is a width of 40 pixels with a height of 30 pixels (or larger but with the same aspect ratio) png images with an alpha channel included.
Adding a Simple Info Widget
The info widget is used in this context to display a label and accompanying static message on the view. Both the label and message needs to be added to the en.lang
properties file. With the info widget added, our view is now as follows:
<?xml version="1.0" encoding="UTF-8"?> <ui xmlns="http://uiprogram.mezzanine.com/View" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://uiprogram.mezzanine.com/View View.xsd"> <view label="view_heading.system_admin_home"> <!-- Menu item for System Admin role --> <menuitem label="menu_item.system_admin_home" icon="UserProfile" order="0"> <userRole>System Admin</userRole> </menuitem> <!-- Info widget to display message from language file --> <info label="info.welcome" value="info.system_admin_welcome"/> </view> </ui>
Our en.lang
properties file now also contains the following two entries:
info.welcome = Welcome: info.system_admin_welcome = Welcome System Admin
Adding a User Invitation Function
Helium users can be invited to an app in the following ways:
- Through a Helium app using the invite built-in function.
- Through the Helium core app web portal.
- Being invited as an initial user through the HeliumDev client.
For the first method, the app developer is responsible for creating an object instance associated with the user and manually calling the invite built-in function. This function is discussed in detail in a later tutorial lesson. This is usually done dynamically using data captured from a form in the Helium app.
The last two methods requires an invite function. For this the developer is responsible for providing a function that returns an instance of the object associated with the user role. In this case an instance of the SystemAdmin
object. Due to the context from which these functions are executed developers usually hard code attribute values or generate them on the fly. This method is therefore only valid for users used for testing. To add such a method, create a new file called UserInvite.mez
under the services folder and add the following content to the file:
unit UserInvite; @InviteUser SystemAdmin inviteSystemAdmin() { SystemAdmin systemAdmin = SystemAdmin:new(); systemAdmin.firstName = "Test"; systemAdmin.lastName = "User"; systemAdmin.mobileNumber = "278212345678"; systemAdmin.emailAddress = "user@domain.com"; return systemAdmin; }
Ignore the use of unit
for now. This is discussed in detail later in this lesson. The function that was added makes use of the @InviteUser
annotation. This marks it as a special function that Helium will use to invite users to the app when using the Helium core web app or the HeliumDev client.
The first line makes use of a special built-in function:
SystemAdmin systemAdmin = SystemAdmin:new();
This line uses the new
built-in function to initialize a new instance of the SystemAdmin
object and assigns it to a variable. Note that usage of the new built-in function is only required for objects and not basic data types.
After initializing the object instance, the attribute values are hard coded. Finally, a return
statement is used to return the populated SystemAdmin
object instance. The return
keyword functions the same as in many other programming languages including Java and C.
Compiling and Running the App
The application is now ready to be tested. First, test that it compiles. To do this start the HeliumDev client and use the build
command. The following output can be expected:
he-dev> build Loading source files... Compiling the application... The project "helium-tut" compiled successfully The last command completed in 0.18 seconds
Consider a scenario where we forgot to add the lang file properties for our info widget. Using the build
command in that case would result in the following output:
he-dev> build Loading source files... Compiling the application... |-----------------------------------------------------------------------------------------------------------------------------------------| | The module "Web" generated the following warnings | |-----------------------------------------------------------------------------------------------------------------------------------------| | # | Message | |-----------------------------------------------------------------------------------------------------------------------------------------| | 1 | [:-1] The translation statement's key info.system_admin_welcome does not exist in the translation for locale en | | 2 | [SystemAdminHome:17] The translation statement's key info.welcome does not exist in the translation for locale en | | 3 | [SystemAdminHome:17] No translation exists for key info.welcome and locale en used for the text output widget's label | |-----------------------------------------------------------------------------------------------------------------------------------------| The last command completed in 0.07 seconds
The detail provided by the error messages can then be used to debug and fix the issues. In this case it's clear that we are missing two entries in our en.lang
properties file.
Once we are happy that the application compiles, we can deploy a snapshot app. To do this use the run
command. When prompted for a pin, enter the pin that was previously set when configuring the HeliumDev client. Note that the run
command also compiles the application before creating a new snapshot. It is therefore not necessary to use the build
command each time before using run
. The build
command is useful to ensure that the app builds when you do not necessarily intend on deploying it immediately.
It's best practice to use the build
command often to ensure that your application is still without compile errors. Debugging multiple errors is significantly more difficult that fixing issues one at a time as they appear.
The following output from the run command can be expected:
he-dev> run Loading source files... Compiling the application... The project "helium-tut" compiled successfully Deploying a sandboxed instance of "helium-tut" to the "localhost" server Loading source files... Publishing a new release of "helium-tut" to the "localhost" server A sandboxed instance of "helium-tut" has been deployed to "localhost". You can access this instance at: http://jm.stb.mezzanineware.com/web-client/app?appId=d873e589-41bb-474f-97e5-ad8f980c1214&roleName=System+Admin The last command completed in 1.11 seconds
Take note of the app id in the url that is displayed after running the app. In this case, d873e589-41bb-474f-97e5-ad8f980c1214
. This id is needed to uniquely identify your app and is necessary when making any support requests or when using the Helium Logging Service. For reference, the documentation here provides general instructions on how to obtain the app id should it be needed.
Using the provided url, access the app. You should be presented with a view similar to the screenshot below.
If you experience a runtime exception or error at any point when testing your app, the Helium Logging Service can be used to help with debugging.
Compile your app regularly to ensure there are no errors.
If you experience any exception or error message while using your app, the Helium Logging Service can be used to help with debugging.
Adding a Presenter
Next we will create a presenter to support our view. Presenters contain functions and variables that represent most of the app logic. To create a presenter, create a file named SystemAdminHome.mez
under the web-app/presenters
folder. Once again, note that there is no restriction to the use of subfolders. A unit can now be declared inside the SystemAdminHome.mez
file:
unit SystemAdminHome;
Note that even though it is not required for the unit and file to have the same name it is recommended. In addition, Unit names have to be unique.
Use subfolders to logically organise the presenter files in your app.
Unit names have to be unique.
Maintain consistency between your presenter file names and unit names.
Connecting a View and Presenter
In this section we show how to link a view with the presenter created in the previous step. Not all presenters need to be linked to a view though. Even if a presenter is not linked to a view its functions and variables can still be accessed. This is discussed under the Referencing Unit Variables and Functions sections. To link a presenter to a view we will modify the <view/>
tag in our SystemAdminHome.vxml
view file as follows:
<view label="view_heading.system_admin_home" unit="SystemAdminHome">
In addition we want to have a function that executes each time the view is loaded. To do this we need to add an init function to our presenter/unit
and then specify the init function in our view:
unit SystemAdminHome; void init() { // Placeholder code to be replaced int i = 0; }
Functions in Helium are not allowed to be empty. For that reason we are adding a placeholder statement in our init function for now. This will be replaced shortly.
<view label="view_heading.system_admin_home" unit="SystemAdminHome" init="init">
Although it is not necessary to use init
in the function name for init functions, it is a useful convention.
Use init
in the function name for your init functions to clearly state their purpose and make code easier to understand.
Adding Unit Variables and Modifying Our Info Widget
In this section we want to modify our view info widget to display a more personalised message to the user using their name and surname. To do this we must modify our presenter with unit variables that wil store the values we want to display, we want to populate these values in our init function and we want to modify our properties file to reference these values for the info widget. Firstly we modify our presenter.
unit SystemAdminHome; SystemAdmin currentSystemAdminUser; string firstName; string lastName; void init() { currentSystemAdminUser = SystemAdmin:user(); firstName = currentSystemAdminUser.firstName; lastName = currentSystemAdminUser.lastName; }
We also add the currentSystemAdminUser
unit variable. This represents the SystemAdmin
object instance for the currently logged in "System Admin"
user. In addition we also have string unit variables that will represent the name and surname of the currently logged in user:
SystemAdmin currentSystemAdminUser; string firstName; string lastName;
To populate the currentSystemAdminUser
variable, we need to use a special built-in function to get the object instance for the currently logged in system admin.
SystemAdmin:user();
We then need to assign it to our unit variable:
currentSystemAdminUser = SystemAdmin:user();
In this case we know that our logged in user will belong to the "System Admin"
role. Helium, however, allows presenters and even views to be shared by different user roles. If for example we invoked the SystemAdmin:user()
built-in function when the currently logged in user was not a system admin, the function would have returned null
. To determine the current role for the logged in user one can use the Mez:userRole()
built-in function. This function returns a string containing the role name as specified inside the @Role
annotation on the model object. See the example below:
string currentLoggedInUserType = Mez:userRole();
In addition to the object instance for the currently logged in user, we retrieve the first name and last name value from this object instance by referencing the attributes that we defined as part of the SystemAdmin
model object:
firstName = currentSystemAdminUser.firstName; lastName = currentSystemAdminUser.lastName;
Now that we have the necessary unit variables that will be populated in the init function we can reference them in our en.lang
properties file by modifying our lang file entry for the info widget:
info.welcome = Welcome: info.system_admin_welcome = Welcome System Admin
info.welcome = Welcome: info.system_admin_welcome = Welcome {SystemAdminHome:firstName} {SystemAdminHome:lastName}
Notice how the curly brackets are used to indicate variable values. Also note the use of both the unit name and the variable name to reference unit variables. At this point it is a good idea to check that the app still compiles by using the build command in the HeliumDev client.
More Complex Info Widget Usage
Now that we have explored one way in which to reference unit variables from a info widget, we can explore two different ways. This first way is by displaying an attribute of our complex unit variable directly. To do this we add an info widget beneath our existing info widget that looks as follows:
<!-- Info widget can also be bound to a unit variable --> <info label="info.mobile_number"> <binding variable="currentSystemAdminUser"> <attribute name="mobileNumber"/> </binding> </info>
Note, at this point, that all headings, labels and other static values that need to be displayed on views are retrieved from a properties file. In our case this is the en.lang
file. Each entry, that needs to be made to this file, might therefore not be explicitly mentioned in the remainder of this tutorial. If unsure, the source code provided with each lesson can be used as reference.
This info widget is bound to the currentSystemAdminUser
variable and references the mobileNumber
attribute for that object.
Another option is to use a unit function to return the value that we are interested in. Once again add the following info widget to the SystemAdminHome
view:
<info label="info.email_address"> <binding function="getSystemAdminEmailAddress"/> </info>
The getSystemAdminEmailAddress
unit function also needs to be added to the SystemAdminHome
presenter:
string getSystemAdminEmailAddress() { return currentSystemAdminUser.emailAddress; }
In this case we returned the attribute value directly. We could also have returned the SystemAdmin
object and specified the attribute to display as part of the info widget.
At this point, when running the app you should see a view similar to the screenshot below.
Referencing Unit Variables and Functions
The scope of unit functions and variables in Helium is global. When referencing unit functions from a different unit or from a view that is not bound to that unit the function needs to be referenced using both the unit name and function name.
MyUnit:myFunction()
Similarly when referencing a unit variable from a different unit, a view that is not bound to that unit or from a properties file the variable needs to be referenced using both the unit name and the variable name.
MyUnit:myVariable
In any other scenario only the unit name or variable name needs to be used.