Engee documentation
Notebook

TabaegraergaergenieFramework

In this article, we will look at creating a web application – an airport scoreboard featuring a table of flights. To familiarise yourself with the basic logic of web applications in GenieFramework, we recommend that you read the introduction article. Here we will focus on working with tables via DataTable, interacting with CSV files and dynamically updating data using an asynchronous loop. We will pay particular attention to why DataTable is used to display the table, rather than simply DataFrame.
board.png

Application Code

using GenieFramework
using Stipple
using Stipple.Tables
using DataFrames
using Dates
using CSV

function generate_flight_data()
    DataFrame(
        "Номер рейса"=>["SU123", "AF456", "BA789"],
        "Назначение"=>["Москва", "Париж", "Лондон"],
        "Время отправления" => [Dates.format(now() + Hour(rand(1:5)), "HH:MM") for _ in 1:3],
        "Статус" => ["Вовремя", "Задержан", "Отменен"]
    )
end

@app begin
    @out current_time = "Текущее время: " * Dates.format(now(), "HH:MM:SS")
    @out flight_table = DataTable(DataFrame())  # Пустая таблица изначально

    @onchange isready begin
        initial_df = generate_flight_data()
        CSV.write("flight_data.csv", initial_df)
        flight_table = DataTable(initial_df)

        @async while true
            sleep(5)
            current_time = "Текущее время: " * Dates.format(now(), "HH:MM:SS")
            df = CSV.read("flight_data.csv", DataFrame, types=String)
            df.Status[rand(1:3)] = rand(["Вовремя", "Задержан", "Отменен"])
            CSV.write("flight_data.csv", df)
            flight_table = DataTable(df)
        end
    end
end

function ui()
    [
        h3("{{current_time}}"),
        table(:flight_table; dense=true, flat=true)
    ]
end

@page("/", ui)

Programme logic: step-by-step analysis

Connecting packages

The following packages are used:

  • GenieFramework: The main framework for creating web applications.
  • Stipple: Provides reactivity and communication between the server and the interface.
  • Stipple.Tables: Provides a DataTable s for displaying tables in the web interface.
  • DataFrames, Dates, CSV: Required for working with tabular data, time formatting and file operations.
    Each package plays its own role, providing a minimal set of tools for the application.
Data generation

The function generate_flight_data() creates a flight table:

  • Returns an object DataFrame with four columns:
    • FlightNumber``: Flight numbers (“SU123”, “AF456”, “BA789”).
    • Destination: Destinations (“Moscow”, “Paris”, “London”).
    • DepartureTime: Departure time in “HH:MM” format, based on the current time plus a random offset (1–5 hours).
    • Status: Flight statuses (“On Time”, “Delayed”, “Cancelled”).
  • This function emulates the initial data for the scoreboard, which will then be updated whilst the application is running.
Data model: variables

The block ‘ @app ’ defines the data model:

  • @out current_time: A reactive variable containing a string with the current time (“Current time: HH:MM:SS”). It is initially set at startup and updated later.
  • @out flight_table: A reactive type variable DataTable, initially empty (DataTable(DataFrame())). It will be populated with data when the application is loaded.
    These variables are available for display in the interface and automatically update it when they change.
Data model: initialisation and updating

The handler @onchange isready controls the application logic:

  • isready s and their roles:
    • isready — this is Stipple’s built-in reactive variable, which initially has the value false. It becomes true when the client (browser) has fully loaded the page and established a connection to the server via WebSocket.
    • @onchange isready means that the code inside the block is executed once when isready changes from false to true, that is, upon the first successful download of the application on the client side. This does not mean that updates are limited to this moment alone. — @onchange it runs the code once, but we can initiate persistent processes within it.
    • We use @onchange isready to ensure that data initialisation and the start of the update cycle occur only after the interface is ready for operation. Without this, the code could execute too early, before establishing communication with the client, which would lead to synchronisation errors.
  • Initialisation:
    • The initial table is created initial_df via generate_flight_data().
    • Data is written to a file flight_data.csv.
    • flight_table is updated with DataTable(initial_df).
  • Asynchronous loop:
  • Starts via @async while true, which creates a background task.
    • Every 5 seconds (sleep(5)):
  • Updates current_time.
    • Data is read from the parameter file types=String to save the string format.
    • The status of one flight is changed at random.
    • The updated data is written to a file and assigned to flight_table``.
    Interface

The function ui() sets the appearance:

  • h3("{{current_time}}"): Displays the current time, which is updated automatically.
  • table(:flight_table; dense=true, flat=true): Creates a table linked to flight_table``.
    • dense=true makes the table compact.
    • flat=true removes shadows for a simple design.
    • DataTable automatically generates headings from column names DataFrame.
Page Registration
  • @page("/", ui): Indicates that the interface is accessible via the root route "/". This completes the configuration of the application.

Why DataTable rather than simply DataFrame?

DataFrame — this is a powerful Julia tool for working with tabular data in code. It is excellent for data manipulation: filtering, sorting and calculations. However, on its own DataFrame It is not intended to be displayed in a web interface. It represents data in the programme’s memory, but it does not have built-in logic for transferring it to the browser or rendering it as an HTML table.

DataTable from Stipple.Tables solves this problem:

  • Reactivity: DataTable integrates with the Stipple reactivity system. When the data in flight_table changes, the interface is updated automatically without the need to manually redraw the table.
  • Formatting for the web: DataTable converts DataFrame into a structure compatible with Quasar (the framework used by Stipple for the interface), adding headers and data in the desired format.
  • Simplicity: Using table(:flight_table) allows you to display data without writing complex HTML or JavaScript code. If we were to use just DataFrame, I would have to manually convert it to an array of dictionaries (Vector{Dict}) and pass it to the interface, which complicates the code.

Therefore, DataFrame — this is the ‘raw’ data, and DataTable — a ‘bridge’ between them and the web interface, providing convenience and automation.

---### How do I launch the app?

Go to the folder containing the current script and run the cell below.

This should open a window displaying the web application.

If it does not open in a separate tab, click on the link in the output cell.

  • match(r"'(https?://[^']+)'" allows you to find a link based on the output of the genie.start function using regular expressions
  • Markdown.parse this is used to display ‘clickable’ links in the output cell.
In [ ]:
using Markdown
cd(@__DIR__)

app_url = string(engee.genie.start(string(@__DIR__,"/app.jl")))

Markdown.parse(match(r"'(https?://[^']+)'",app_url)[1])
Out[0]:

using Markdown
cd(@DIR)

app_url = string(engee.genie.start(string(@DIR,"/app.jl")))

Markdown.parse(match(r"'(https?://[^']+)'",app_url)[1])

Conclusion

The application demonstrates the creation of an airport scoreboard using a dynamic table. We looked at generating data, saving it to a file, updating it via an asynchronous loop, and displaying it using DataTable. Each block of code performs its own task, providing a minimalistic and functional implementation. DataTable This proved to be a key element that simplifies the integration of tabular data into the web interface, something that cannot be achieved with the standard DataFrame``. To fully understand the logic, we recommend taking a quick look at the entire code — this will help you see how all the parts are interconnected.