Документация Engee
Notebook

Табло аэропорта с динамической таблицей в GenieFramework

В этой статье мы рассмотрим создание веб-приложения — табло аэропорта с таблицей рейсов. Для ознакомления с базовой логикой веб-приложений в GenieFramework, рекомендуем ознакомиться с вводной статьёй. Здесь же мы сосредоточимся на работе с таблицами через DataTable, взаимодействии с файлами CSV и динамическом обновлении данных с использованием асинхронного цикла. Особое внимание уделим тому, почему для отображения таблицы используется именно DataTable, а не просто DataFrame.

board.png

Код приложения

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)

Логика программы: пошаговый разбор

Подключение пакетов

Используются следующие пакеты:

  • GenieFramework: Основной фреймворк для создания веб-приложений.
  • Stipple: Обеспечивает реактивность и связь между сервером и интерфейсом.
  • Stipple.Tables: Предоставляет DataTable для отображения таблиц в веб-интерфейсе.
  • DataFrames, Dates, CSV: Нужны для работы с табличными данными, форматирования времени и операций с файлами.
    Каждый пакет играет свою роль, обеспечивая минимальный набор инструментов для приложения.

Генерация данных

Функция generate_flight_data() создает таблицу рейсов:

  • Возвращает объект DataFrame с четырьмя колонками:
    • FlightNumber: Номера рейсов ("SU123", "AF456", "BA789").
    • Destination: Пункты назначения ("Москва", "Париж", "Лондон").
    • DepartureTime: Время вылета в формате "HH:MM", основанное на текущем времени плюс случайное смещение (1–5 часов).
    • Status: Статусы рейсов ("Вовремя", "Задержан", "Отменен").
  • Эта функция эмулирует начальные данные для табло, которые затем будут изменяться в процессе работы приложения.

Модель данных: переменные

Блок @app определяет модель данных:

  • @out current_time: Реактивная переменная, содержащая строку с текущим временем ("Текущее время: HH:MM:SS"). Изначально задается при запуске и обновляется позже.
  • @out flight_table: Реактивная переменная типа DataTable, изначально пустая (DataTable(DataFrame())). Она будет заполнена данными при загрузке приложения.
    Эти переменные доступны для отображения в интерфейсе и автоматически обновляют его при изменении.

Модель данных: инициализация и обновление

Обработчик @onchange isready управляет логикой приложения:

  • isready и его роль:
    • isready — это встроенная реактивная переменная Stipple, которая изначально имеет значение false. Она становится true, когда клиент (браузер) полностью загрузил страницу и установил соединение с сервером через WebSocket.
    • @onchange isready означает, что код внутри блока выполняется один раз, когда isready изменяется с false на true, то есть при первой успешной загрузке приложения на стороне клиента. Это не означает, что обновления ограничиваются только этим моментом — @onchange запускает код однократно, но внутри него мы можем инициировать постоянные процессы.
    • Мы используем @onchange isready, чтобы гарантировать, что инициализация данных и запуск цикла обновления происходят только после того, как интерфейс готов к работе. Без этого код мог бы выполниться слишком рано, до установления связи с клиентом, что привело бы к ошибкам синхронизации.
  • Инициализация:
    • Создается начальная таблица initial_df через generate_flight_data().
    • Данные записываются в файл flight_data.csv.
    • flight_table обновляется с помощью DataTable(initial_df).
  • Асинхронный цикл:
    • Запускается через @async while true, что создает фоновую задачу.
    • Каждые 5 секунд (sleep(5)):
      • Обновляется current_time.
      • Читаются данные из файла с параметром types=String для сохранения строкового формата.
      • Случайно изменяется статус одного рейса.
      • Обновленные данные записываются в файл и присваиваются flight_table.

Интерфейс

Функция ui() задает внешний вид:

  • h3("{{current_time}}"): Отображает текущее время, которое обновляется автоматически.
  • table(:flight_table; dense=true, flat=true): Создает таблицу, привязанную к flight_table.
    • dense=true делает таблицу компактной.
    • flat=true убирает тени для простого дизайна.
    • DataTable автоматически формирует заголовки из имен колонок DataFrame.

Регистрация страницы

  • @page("/", ui): Указывает, что интерфейс доступен по корневому маршруту "/". Это завершает настройку приложения.

Почему DataTable, а не просто DataFrame?

DataFrame — это мощный инструмент Julia для работы с табличными данными в коде. Он отлично подходит для манипуляций с данными: фильтрации, сортировки, вычислений. Однако сам по себе DataFrame не предназначен для отображения в веб-интерфейсе. Он представляет данные в памяти программы, но не имеет встроенной логики для передачи их в браузер или рендеринга в виде HTML-таблицы.

DataTable из Stipple.Tables решает эту задачу:

  • Реактивность: DataTable интегрируется с системой реактивности Stipple. При изменении данных в flight_table интерфейс автоматически обновляется без необходимости вручную перерисовывать таблицу.
  • Форматирование для веба: DataTable преобразует DataFrame в структуру, совместимую с Quasar (фреймворк, используемый Stipple для интерфейса), добавляя заголовки и данные в нужном формате.
  • Простота: Использование table(:flight_table) позволяет отобразить данные без написания сложного HTML или JavaScript-кода. Если бы мы использовали просто DataFrame, пришлось бы вручную преобразовывать его в массив словарей (Vector{Dict}) и передавать в интерфейс, что усложняет код.

Таким образом, DataFrame — это "сырые" данные, а DataTable — "мост" между ними и веб-интерфейсом, обеспечивающий удобство и автоматизацию.


Как запустить приложение?

Перейдите в папку с текушим скриптом и выполните ячейку ниже.

В результате у вас должно открыться окно с веб-приложением.

Если оно не открылось в отдельной вкладке, нажмите на ссылку из ячейки вывода.

  • match(r"'(https?://[^']+)'" позволяет найти ссылку, исходя из вывода работа функции engee.genie.start, используя регулярные выражения
  • Markdown.parse используется для отображения "кликабельных" ссылок в ячейке вывода.
In [ ]:
using Markdown
cd(@__DIR__)

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

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

Заключение

Приложение демонстрирует создание табло аэропорта с динамической таблицей. Мы рассмотрели генерацию данных, их сохранение в файл, обновление через асинхронный цикл и отображение с помощью DataTable. Каждый блок кода выполняет свою задачу, обеспечивая минималистичную и функциональную реализацию. DataTable оказался ключевым элементом, упрощающим интеграцию табличных данных в веб-интерфейс, чего нельзя достичь с обычным DataFrame. Для полного понимания логики советуем мельком взглянуть на код целиком — это поможет увидеть, как все части связаны между собой.