Building reactive UIs

Genie Framework implements a low-code API that helps Julia developers quickly and easily create interactive data dashboards and data-centric applications, as well as web-based user interfaces for Julia software. Moreover, it offers a large collection of user interface elements (such as inputs, buttons, sliders, tabs, data tables, responsive layouts, and many more) and powerful plotting capabilities.

To implement a reactive UI, you need to define:

  • Interactive UI components
  • Reactive variables to hold the component states
  • Reactive code to handle interaction

Genie Framework provides a small but complete set of macros in its low-code API with which you can implement all of the above.

The snippet below illustrates the structure of an app with a reactive UI.

app.jl

module App
#setup of the Genie Framework environment
using GenieFramework
@genietools

# reactive code
@app begin
    @in N = 0
    @out msg = ""
    @onchange N begin
        msg = "N = $N"
    end
end

# UI components
function ui()
    [
        cell([
                p("Enter a number")
                textfield("N", :N )
            ])
        cell([
                bignumber("The value of N is", :N)
            ])
    ]
end

# definition of root route
@page("/", ui)
end

Let's break down this code.

Setting up the Genie development environment

The GenieFramework.jl package is comprised of many sub-packages that implement the different blocks in the framework. The include of GenieFramework.jl exports a select number of sub-packages whose functions are used to build the app. Then, the call to @genietools configures the javascript, icons, fonts and other assets that'll be loaded by the app.

Reactive code

The @app macro delimits the block where reactive code is defined. In it, the @in, and @out macros define reactive variables whose value is exposed to the UI. These variables are used to store in the backend the state of UI components, and Javascript code that connects the front end to the reactive variables is automatically generated. Moreover an @in variable can be modified from the UI, whereas @out variables are read only.

In addition, the @private macro defines non-reactive variables that are not exposed to the UI. These can be used to store data not meant to be shared between users.

Besides the reactive variables, the @onchange macro defines a block of reactive code that is executed when a reactive variable changes in value. This value change can come from the Julia code or from the front-end code; both will trigger the reactiver code execution.

Reactive variables tagged with @in or @out can only be modified within a block delimited by the @onchange macro. Any changes made outside of it will not be reflected in the UI.

Defining the UI layout and components

There's two ways to implement the UI in Genie Framework: in pure Julia using the low-code API, or in HTML. The example code we're examining showcases the Julia option.

There is an extensive library of UI components that can be instantiated with Julia function calls. These can be divided in two groups:

  • Layout: functions that set the page layout by defining rows, cells or columns.
  • Components: functions that generate UI components such as input fields, sliders or plots.

The complete list of calls in the API is available in the API/Components section. These functions return the HTML code for the component, which can be inspected by printing their output:

julia> print(cell())
<div class="col col-12 col-sm"></div>

julia> print(textfield("N", :N ))
<q-input label="N" v-model="N"></q-input>

To define the reactive UI in HTML instead of using Julia calls, you have to write it to a ui.html file and include it with @page("/","ui.html"). The code should be comprised of standard HTML constructs, and components from the Quasar framework. For the current example, the UI defined in HTML is:

<div class="col col-12 col-sm">
    <h4>Enter a number</h4>
    <q-input label="N" v-model="N" style="width:200px"></q-input>
</div>
<div class="col col-12 col-sm">
    <st-big-number :number="N" title="The value of N is"></st-big-number>
</div>

Finally, you can also call the low-code API from HTML files to add components:

 <% textfield("N", :N ) %> 

Declaring a route

A route is an endpoint address for browsers which, when accessed, will return the page to be rendered. The @page macro defines a route at the root / that will return the page code generated by the ui function.

Running the app

To run the app, first navigate to the directory where the code is and execute

julia --project -i -e 'using Pkg; Pkg.add("GenieFramework")'

Then, launch Julia with julia --project and run the app:

julia> using GenieFramework
julia> Genie.loadapp() # load app
julia> up() # start server

While developing, you can make changes to a running app's code. They will they will be automatically loaded and the UI will refresh.