Табло аэропорта с динамической таблицей в 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. Для полного понимания логики советуем мельком взглянуть на код целиком — это поможет увидеть, как все части связаны между собой.