In general, all bricks shall be named using Title Case, with capital letters for all words and spaces between words.
Functions and Actions
Function Names use Title Case, with capital letters for all words and spaces between words.
outputs are in lowercase, with spaces between words.
Model Names use Title Case, with capital letters for all words and spaces between words.
relations are in lowercase, with spaces between words.
Component Name use Title Case, with capital letters for all words and spaces between words.
Properties use Title Case, with capital letters for all words and spaces between words.
UI Components have two main purposes:
- Allow the creation of reusable components
- Make it easier to layout elements
For example, using a UI Component to store the content of a form allows to easily align the different text fields and form elements. The 'form component' can then be dropped on a screen and resized in a coherent manner, without needing to control each form element individually.
Reusing UI Components: exposing children object properties
It is quite rare that we want to reuse a UI Component as is in multiple locations. In most cases, we want to parameterise in one or both of two ways:
- We want to make it themable
- We want to have different texts (or any other property) in the different instances of the UI Component
Customizing the theme
If a UI Component is to be reused by multiple projects, the best practice is to add a custom property to the UI Component (e.g. "OK button theme class"), and map it to the relevant UI Elements using the f(x) of their Theme Class property.
Within a single project, another option (to be used with care) is to establish a convention when naming the theme classes, and use that convention within the UI Component.
If only very specific properties must be settable, the recommendation is to add as many custom properties as needed (e.g. "OK Button Font Size"), and map those to the appropriate UI Element within the UI Component.
Customizing texts (or other properties)
When text or other properties of the UI Component's internal UI Elements must be set, use the following approach:
- Add a custom property in the Context of the UI Component (e.g. "Form Title")
- Map that property to the appropriate UI Component, using the f(x) of the relevant property.
Exposing internal events
Beyond properties, the UI Component's context can define Events.
Those events can be triggered from within the UI Component using Dispatch Event actions.
Within the parent Screen or UI Component, the application can respond to the events by configuring the f(x) on the UI Component's events.
Upon creating a data model, it is recommended to also create a set of helper logic bricks to make it easier to manipulate that data. The standard programming terminology is:
- Constructors are used to construct objects from a model and the relevant parameters to should be set. We may typically have multiple constructors to accommodate different criteria.
- Getters are used to retrieve information from an object.
- Setters are used to modify information within an object.
We recommend the following practices and naming conventions to make it easy to manipulate data.
Given the following business model:
It is good practice to create the following bricks:
- Constructor: a
Create Geolocationaction, taking "Longitude", "Latitude" and "Altitude" as inputs, and outputting a persisted "Geolocation" object.
- Getter: a
Geolocation's Attributesfunction, taking a "Geolocation" object as input, and outputting "Longitude", "Latitude" and "Altitude"
- Setters: a
Set Geolocation's Attributesaction, taking a "Longitude", "Latitude", "Attribute" and "Geolocation" as input, and outputting a "Geolocation" object.
Even though you will often create multiple specialised variants of those bricks, those three base bricks provide a very good starting point. The sections below dive into those elements in a bit more detail.
Constructors are logic bricks that typically contain a Create Local Object brick, as well as a few Set Object Property and Persist bricks.
Constructors should be named as:
Create <<name of business model>> from <<name of input parameters>>
Create Geolocation from City Name
Create Temperature from °C
Here is a potential Constructor action for creating a Geolocation object from a Longitude and Latitude:
As the number of actions and parameters grows, you may also want to stack the bricks instead (with the drawback of having the Control Flow getting a bit in the way):
Getters allow retrieving information from the object. Those can be simple properties (like Latitude in our example above), or higher-level (and often higher-valued as well) information.
A function that provides all (or most) attributes at once should be named as:
<<name of business model>>'s Attributes.
Getters of individual or (subset of) attributes should be named as:
<<name of business model>>'s <<property name>>.
When transforming the output using a different representation or unit, this convention applies:
<<name of business model>> as <<unit>>.
Geolocation's Position(might for instance provide only Longitude and Latitude, but not Altitude)
Geolocation as String(provides a location with the following format: 46.5172214°N, 6.5621934°E)
Geolocation as Address(provides a location with the following format: PSE D, 1015 Ecublens, Switzerland)
Temperature as °F(provides a temperature as Fahrenheit)
Here is a sample getter that retrieves a single property:
Many functions need to retrieve more than one property of an object. As such, it is often more convenient to define a single function that provides all attributes. It is important to note that there is no harm in leaving outputs unconnected within a function. Retrieving all properties at once:
Setters are used to modify the attributes of an object.
Setter's naming convention is similar to that of constructors and getters, with conventions of the form:
Set <<name of business model>>'s <<attribute>>
Set <<name of business model>> from <<high-level object>>
Set Geolocation's Attributes(when setting all attributes at once)
Set Geolocation's Position(when setting both the Latitude and Longitude)
Set Geolocation from Address
Set Temperature from Fahrenheit
Here is a sample setter:
Draw still isn't feature complete. This section therefore lists the main limitations and possible workarounds.
Positioning and resizing
At the moment, the positioning and resizing capability is very powerful, but also very tedious. It is typically not practical to build a fully responsive application that works on a large range of screen sizes. The following tips may help:
- Percentage sizes will work in most cases
- Chain objects to one another using
f(x), e.g. to allow
- Use UI Components to group objects together. Note that while it makes it easier to position objects, UI Components also isolate internal context and events (see above), so they do require a bit of thinking.
- If you need very different layouts on phones and laptops, build separate apps
Refactoring typically occur on different levels:
- Reusing a part of a function in another function
- Updating data models
- Updating the UI
It is good practice to separate UI interactions from logic. Starting with this idea in mind reduces the probability of having to redo a function because we initially put all operations within an
On Click f(x).
In terms of data interactions, preparing and managing constructors, getters and setters separately greatly reduce the amount of rework that is required.
When refactoring a Data Model, the following approach is recommended:
Adding a property:
- Nothing to do here, but don't forget to update the Constructors, Getters and Setters accordingly.
Removing a property
- In the data model editor, rename the property to "my property OLD"
- Look for usage of the property using Cypher queries - UsefulCypherQueries.html
- Update all locations accordingly
- Once all usages have been removed, delete the property. This will delete all instances of that property in the DB.
Changing a property's type, or refactoring a property into a separate business model is done by combining the approaches above, i.e. by adding the new version and removing the old usage.
UI Components and Themes are the best way to encapsulate and reuse information.
Lists are scrollable by default. Screens however are not, as they are meant to support apps, which usually have scrolling in specific locations, and not globally for the whole app. If you need a scrollable pane, you must for the moment use UI Components. The simplest approach is the following:
- Screen contains 1 full screen UI Component, and nothing more
- Full screen UI Component contains any additional UI Component
Local vs Global objects
By default, the
Create Local Object brick doesn't persist the object on the Orchestrator.
Importantly as well, files (typically those coming from
Take Camera Picture) must be persisted explicitly.
The recommendation is to always persist all objects by default within the constructor. There are obviously cases where objects should not be stored in the Orchestrator, some of which are documented in the Olympe Best Practices, but those arise in slightly more advanced scenarios.
When building constructors that don't include the Persist operation, we recommend making it explicit by putting 'Local' in the constructor's name:
Create Local <<name of business model>>.
Using multiple users and permissions
Draw offers the ability to create multiple users for the application. By default, objects created within the application by one particular user are only visible to that user. Roles and permissions however need to be implemented using Code framework, and be exposed in Draw via custom bricks.
Working with multiple browser tabs
It is often very convenient to open multiple tabs or windows to avoid having to switch from one editor to another. Draw stores the current location, such that we can reload the page without changing screens. However, this is a global setting at the moment, so if you wish to work in different browser tabs, it is recommended to use multiple browsers (e.g. Firefox, Chrome and Safari) such that page refreshes work as one would expect.
Since there is no main loop, we recommend putting general functions (e.g. registration of a listener, starting a Timer, ...) in the screen's
On Load function.
We recommend exporting the different bricks (screens, UI components, functions, etc.) individually, in order to make the export granular.
At the moment, Draw supports importing projects into one another. This allows creating one project acting as a library of reusable bricks, which can then be reused across multiple other projects. Versions are not fully functional at the moment, so updates to the library project are immediately reflected in the importing projects as well.
Similarly, double-clicking on an imported function or UI Component will open that function or UI Component for edition in the other project.