Adding reactive pages

In Genie Framework, the Stipple.jl package implements a reactive UI layer that enables dynamic interactivity with the UI. This is useful for building apps that require real-time updates, such as dashboards, data visualizations, and simulations.

We refer to this type of interfaces as reactive, since the app instantly reacts to a user action by executing some code and updating the UI. All without having to reload the page. This is made possible by the Javascript code provided by Stipple.jl that enables two-way synchronization between the browser and the server.

There are several possible patterns for apps with reactive pages. For more details on building reactive apps, see the Your first dashboard guide and the docs.

Single page apps

When building a simple app like a dashboard with some controls and plots, it might be sufficient to keep all the logic in a single file. For instance, you can rewrite the example app from the "Your first app" guide into a single-file reactive app as:

app.jl
module App
using GenieFramework
#import your Julia code
include("lib/StatisticAnalysis.jl")
using .StatisticAnalysis
@genietools

# Implement reactive code
@app begin
    @in N = 0
    @out m = 0.0
    @onchange N begin
        m = calc_mean(gen_numbers(N))
    end
end

# Define the UI
function ui()
    cell([
        textfield("How many numbers?", :N),
        p("The average of {{N}} random numbers is {{m}}"),
    ])
end

# Define the route
@page("/", ui)
end

Alternatively, you can place the UI code in a separate file and include it as

app.jl
@page("/", "ui.jl")
ui.jl
    cell([
        textfield("How many numbers?", :N),
        p("The average of {{N}} random numbers is {{m}}"),
    ])

Note that with this reactive page you don't have to set up forms or care about POST requests. The reactive variables are automatically synchronized and updated when the user enters a new number.

Multi page apps

While it is possible to implement all pages in the app.jl file and add keep adding new routes and UI definitions, this can quickly become unwieldy. For more complex apps, it is recommended to split the code into multiple files following the MVC architecture.

Suppose you have the first demo app, and you want to add the previous example as a new reactive page. All you have to do is

  1. Move the page's logic into a new controller module.

Controllers for reactive pages do not implement a specific handling function for rendering the view. The @app block will be evaluated when the page is loaded, and the reactive variables and handlers within it will be made available to the UI.

controllers/Reactive.jl
module Reactive
using GenieFramework
using .Main.App.StatisticAnalysis
@genietools

@app begin
    @in N = 0
    @out m = 0.0
    @onchange N begin
        m = calc_mean(gen_numbers(N))
    end
end

end
  1. Add a new view file with the page's UI.
pages/reactive.jl
    cell([
        textfield("How many numbers?", :N),
        p("The average of {{N}} random numbers is {{m}}"),
    ])
  1. Import the controller into app.jl and add a route linking it to the UI.

Routes for reactive pages are added with the @page macro. Unlike route, which takes a controller's function as a route handler, @page takes the entire module.

app.jl

module App
using GenieFramework
include("lib/StatisticAnalysis.jl")
using .StatisticAnalysis
include("controllers/Reactive.jl")
using .Reactive

# . . .
# code for other routes goes here
# . . .

# add route for the reactive page
@page("/reactive", "pages/reactive.jl", Stipple.ReactiveTools.DEFAULT_LAYOUT(), Main.App.Reactive)

end

This is what the final file structure would look like

.
├── app.jl
├── controllers
│   ├── AnalysisController.jl
│   └── Reactive.jl
├── lib
│   └── StatisticAnalysis.jl
└── pages
    ├── form.jl.html
    ├── numbers.jl.html
    ├── reactive.jl
    └── result.jl.html