Табло аэропорта с динамической таблицей в GenieFramework¶
В этой статье мы рассмотрим создание веб-приложения — табло аэропорта с таблицей рейсов. Для ознакомления с базовой логикой веб-приложений в GenieFramework, рекомендуем ознакомиться с вводной статьёй. Здесь же мы сосредоточимся на работе с таблицами через DataTable
, взаимодействии с файлами CSV и динамическом обновлении данных с использованием асинхронного цикла. Особое внимание уделим тому, почему для отображения таблицы используется именно DataTable
, а не просто DataFrame
.
Код приложения¶
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
используется для отображения "кликабельных" ссылок в ячейке вывода.
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
. Для полного понимания логики советуем мельком взглянуть на код целиком — это поможет увидеть, как все части связаны между собой.