StippleUI.jl
StippleUI — это библиотека элементов реактивного пользовательского интерфейса для Stipple.jl.
Вместе с
она образует фреймворк Genie, эффективное и полнофункциональное решение для создания красивых, адаптивных, реактивных, высокопроизводительных интерактивных информационных панелей на чистом языке Julia.
StippleUI
предоставляет более 30 элементов пользовательского интерфейса, включая формы и элементы ввода (кнопка, ползунок, флажок, переключатель, группа переключателей, диапазон), списки, таблицы данных, компоненты более высокого уровня (значки, баннеры, карточки, диалоговые окна, фишки, значки) и элементы макета (строка, столбец, информационная панель, заголовок, пробел) из фреймворка Quasar.
Основные изменения в версиях 0.24 и выше
Компоненты
-
Общее: синхронизация дополнительных полей теперь выполняется не с помощью синтаксиса
fieldname.sync
, а с помощьюv-model:fieldname
. Это уже учтено в API компонентов, напримерpaginationsync
вtable()
. Если вы вручную добавили такие поля, потребуется адаптировать код. -
У компонента Table в Quasar имя атрибута содержимого изменилось с
data
наrows
. Соответственно, все ссылки в шаблонах в долгосрочной перспективе следует изменить наrows
, например,table(:mytable, @showif("mytable.data.length"))
следует изменить наtable(:mytable, @showif("mytable.data.length"))
. Однако мы ввели наблюдатель, который задает второе поле с именем data для поля rows, что в большинстве случаев позволяет сохранить работоспособность старого кода. Единственным недостатком такого решения является то, что при синхронизации содержимого таблицы обратно с сервером отправляется двойной объем данных, поэтому в будущем это вспомогательное средство может быть упразднено.
Дополнительная помощь в миграции
-
Файл сведений Stipple: https://github.com/GenieFramework/Stipple.jl/blob/master/README.md
-
Руководство по обновлению Quasar: https://quasar.dev/start/upgrade-guide/
-
Руководство по миграции Vue: https://v3-migration.vuejs.org/
Использование
Компоненты Quasar
В StippleUI определены функции для большинства компонентов Quasar Vue. Имена большинства функций совпадают с именами их аналогов в Quasar, например btn()
для q-btn
, tab()
для q-tab
и т. д. Некоторым функциям для ясности были даны другие имена, например textfield()
для q-input
с текстовым типом и numberfield()
для q-input
с числовым типом. У большинства функций есть именованные позиционные аргументы, которые служат для определения общих атрибутов, например label
или fieldname
. Помимо этого, все функции имеют общую схему вызова для неименованных и именованных аргументов:
-
Если компонент поддерживает содержимое, первый неименованный позиционный аргумент передается в него как содержимое.
-
Любой последующий аргумент, не являющийся массивом, передается в компонент как параметр.
-
Любой неименованный аргумент-массив объединяется и передается как содержимое.
-
Если компонент не поддерживает содержимое, например
btn()
, все неименованные аргументы, кроме массивов, передаются как параметры. (Путем передачи массивов можно определять шаблоны для компонента. Дополнительные сведения см. в документации к Vue.js.) -
Именованные аргументы передаются практически так же, как выражение Julia, за следующими исключениями:
-
__
преобразовывается в-
. -
!
в середине именованного аргумента преобразовывается в.
. -
!
в конце именованного аргумента означает, что его следует рассматривать как выражение JS. -
Если значением именованного аргумента является символ, это означает, что аргумент следует рассматривать как выражение JS.
-
Нестандартные символы, которые не допускаются в именах переменных Julia, можно использовать посредством нотации
var""
, напримерspan(var"v-html" = "My Text with <br>new lines")
-
Большинство имен переменных, содержащих дефис в синтаксисе Vue, заменяются автоматически, например
"leftlabel" => "left-label"
. Все стандартные сопоставления можно найти вStippleUI.API.ATTRIBUTES_MAPPINGS
.
-
Примеры
julia> btn("Just do it!", @click(:mybutton), loading = :mybutton) |> println
<q-btn :loading="mybutton" label="Just do it!" v-on:click="mybutton = true"></q-btn>
julia> textfield("Label", :mytext) |> println
<q-input label="Label" v-model="mytext"></q-input>
Что делать, если нет нужного компонента
Если компонент Quasar еще не реализован в StippleUI, вы все равно можете использовать функцию quasar()
, чтобы добавить его в пользовательский интерфейс:
julia> quasar(:btn, label = "Action!") |> println
<q-btn label="Action!"></q-btn>
Очень похожим образом можно интегрировать компоненты Vue или любой другой компонент HTML:
julia> vue(:calender, date = "today", "This is still a dream!")
"<vue-calender date=\"today\">This is still a dream!</vue-calender>"
julia> xelem(:br)
"<br></br>"
Привязки
Присваивать значения свойствам компонентов можно двумя способами:
-
присвоение строкового значения:
attribute = "lorem ipsum"
; -
присвоение выражения JavaScript:
-
путем присвоения
Symbol
-
attribute = :input
;
-
путем добавления символа
!
к атрибуту
attribute! = "input"
.
В большинстве случаев этот синтаксис используется для прямой привязки переменных приложения, но можно привязывать и более сложные выражения, например элементы массива (имейте в виду, что в JS индексация начинается с 0): attribute = Symbol("data[0]")
attribute = R"data[0]"
attribute! = "data[0]"
Строковый макрос R""
— это удобный способ определения символов.
Вспомогательные макросы
Во Vue.js определено несколько сокращений, которые обычно начинаются с символа @
. В Julia символ @
зарезервирован для макросов. Поэтому мы решили использовать макросы для обеспечения схожего синтаксиса. Однако мы также предоставляем макросы для добавления атрибутов с другими «запрещенными» символами, такими как .
или -
. Дополнительные сведения см. в docstring макросов @click
, @on
, @iif
, @els
, @elsiif
, @recur
, @text
, @bind
, @data
и @showif
. Чтобы проверить правильность обработки, введите в REPL пробное выражение, например:
julia> span(@showif(true))
"<span v-show=\"true\"></span>"
julia> span(@click(:mybutton))
"<span v-on:click=\"mybutton = true\"></span>"
Flexgrid в Quasar
В Quasar реализована сеточная система под названием Flexgrid, которая позволяет легко определять пользовательские интерфейсы посредством классов.
Сетка может быть вертикальной (class = "column"
) или горизонтальной (class = "row"
). Размер дочерних элементов внутри контейнера устанавливается путем задания класса "col"
для равномерного распределения элементов или "col-6"
для фиксированного значения, кратного 1/12 размера контейнера (в данном случае 6
, то есть 50 %). Кроме того, Quasar позволяет изменять размеры дочерних элементов в зависимости от размера контейнера путем добавления условия размера после класса col, например "col-md-3"
или просто "col-md"
. В API StippleUI определены атрибуты col
, xs
, sm
, md
, lg
, xl
, упрощающие определение этого класса, например:
row(htmldiv(col = 2, md = 4)) |> println
# <div class="row"><div class="col-2 col-md-4"></div></div>
Интервалы между дочерними элементами добавляются путем задания class="gutter-md". Для дочерних элементов в гибких контейнерах (row
или column
) необходимо задать class = "gutter-col-md"
. В данном случае также определен атрибут, который учитывает это различие и автоматически выбирает правильную настройку.
julia> row(gutter = "md", htmldiv(col = 2, md = 4), "Hello World") |> println
<div class="row q-col-gutter-md" Hello World><div class="col-2 col-md-4"></div></div>
Помимо этого, дочерние элементы могут отображаться некорректно, если у них есть настройки фона. Связано это с тем, как в Quasar задаются поля и внутренние отступы. Выходом из положения является инкапсуляция дочерних элементов в дополнительный элемент div
, что можно легко сделать с помощью макроса @gutter
.
julia> row(gutter = "md", @gutter htmldiv(col = 2, md = 4, "Hello World")) |> println
<div class="row q-col-gutter-md"><div class="col-2 col-md-4"><div>Hello World</div></div></div>
Дополнительные сведения см. в docstring.
Код JavaScript
Vue.js дает возможность встраивать функции JavaScript, которые вызываются вручную (методы, methods
) или автоматически при наступлении определенных событий, например watch
, mounted
, created
, computed
. Такой код можно легко определить с помощью соответствующих макросов @methods
, @watch
, @mounted
, @created
и @computed
, например:
@methods """
logdemo: function(text) {
console.log("Text from Stipple: " + text)
return text
},
squaredemo: function(number) {
console.log("The square of " + number + " is " + number**2)
return number**2
}
"""
@created """"
console.log('This app has just been created!')
"""
Дополнительные сведения см. в демонстрации редактируемого дерева. Эти макросы также работают с явными моделями, например:
@created MyApp """"
console.log('This app has just been created!')
"""
Пользовательские события
Макрос @event
поддерживает пользовательские события.
@event :uploaded begin
println("Files have been uploaded!")
end
Ниже показан код Julia, который выполняется при перенаправлении события из клиента на сервер. Обычно события в клиенте происходят из определенных компонентов Vue, например q-uploader
. Их можно перенаправлять, вызывая макрос @on
с двумя символьными аргументами.
julia> uploader("Upload files", url = "/upload" , @on(:uploaded, :uploaded))
"<q-uploader url=\"/upload\" v-on:uploaded=\"function(event) { handle_event(event, 'uploaded') }\">Upload files</q-uploader>"
События также можно инициировать вручную, вызывая handle_event(event, 'uploaded')
на стороне клиента.
Дополнительные сведения см. в демонстрации отправки файла. Макрос @event
также работает с явными моделями, например:
@event MyApp :uploaded begin
println("Files have been uploaded to MyApp!")
end
Пример приложения
Следующий фрагмент кода иллюстрирует создание реактивного пользовательского интерфейса на основе StippleUI. Обработчики активируются тремя способами.
-
Каждый раз при вводе данных в поле ввода активируется функция, которая добавляет строку, обратную введенной, в поле вывода.
-
При нажатии клавиши
Enter
в поле ввода выходная строка меняется на обратную. -
При нажатии кнопки действия выходная строка меняется на обратную. StippleUI
using Stipple, StippleUI
@vars Inverter begin
process = false
input = ""
output::String = "", READONLY
end
function handlers(model)
on(model.input) do input
model.output[] = input |> reverse
end
onbutton(model.process) do
model.output[] = model.output[] |> reverse
end
model
end
function ui()
row(cell(class = "st-module", [
textfield(class = "q-my-md", "Input", :input, hint = "Please enter some words", @on("keyup.enter", "process = true"))
btn(class = "q-my-md", "Action!", @click(:process), color = "primary")
card(class = "q-my-md", [
card_section(h2("Output"))
card_section("Variant 1: {{ output }}")
card_section(["Variant 2: ", span(class = "text-red", @text(:output))])
])
]))
end
route("/") do
model = Inverter |> init |> handlers
page(model, ui()) |> html
end
Genie.isrunning(:webserver) || up()
StippleUIParser
Инструменты
-
parse_vue_html
-
test_vue_parsing
-
prettify
Совсем недавно появился новый инструмент — StippleUIParser. Он преобразовывает код HTML в соответствующий код Julia и оптимизирует его. Это вспомогательный инструмент для переноса демонстрационного кода из Интернета в приложения Stipple/Genie.
Анализ и преобразование кода HTML Vue в код Julia
julia> using StippleUI.StippleUIParser
julia> doc_string = """
<template>
<div class="q-pa-md">
<q-scroll-area style="height: 230px; max-width: 300px;">
<div class="row no-wrap">
<div v-for="n in 10" :key="n" style="width: 150px" class="q-pa-sm">
Lorem @ipsum \$dolor sit amet consectetur adipisicing elit.
</div>
<q-btn color=\"primary\" label=\"`Animate to \${position}px`\" @click=\"scroll = true\"></q-btn>
<q-input hint=\"Please enter some words\" v-on:keyup.enter=\"process = true\" label=\"Input\" v-model=\"input\"></q-input>
<q-input hint=\"Please enter a number\" label=\"Input\" v-model.number=\"numberinput\" class=\"q-my-md\"></q-input>
</div>
</q-scroll-area>
</div>
</template>
""";
julia> parse_vue_html(html_string, indent = 2) |> println
template(
Stipple.Html.div(class = "q-pa-md",
scrollarea(style = "height: 230px; max-width: 300px;",
Stipple.Html.div(class = "row no-wrap", [
Stipple.Html.div(var"v-for" = "n in 10", key! = "n", style = "width: 150px", class = "q-pa-sm",
"Lorem @ipsum dolor sit amet consectetur adipisicing elit."
)
btn(raw"`Animate to ${position}px`", color = "primary", var"v-on:click" = "scroll = true")
textfield("Input", :input, hint = "Please enter some words", var"v-on:keyup.enter" = "process = true")
numberfield("Input", :numberinput, hint = "Please enter a number", class = "q-my-md")
])
)
)
)
Тестирование результата анализа
Существует также инструмент test_vue_parsing()
для тестирования успешности анализа:
julia> test_vue_parsing(raw"""<a :hello="I need $$$">asap</a>""") Original HTML string: <a :hello="I need $$$">asap</a> Julia code: a(hello! = raw"I need $$$", "asap" ) Produced HTML: <a :hello="I need $$$"> asap </a>
Он учитывает все особенности синтаксиса привязки и тег сохранения HTML <pre>
.
julia> test_vue_parsing(raw"""<q-test :hello-world="I need $$$"> asap\n or\ntoday <pre>asap\n or\ntoday </pre></q-test>"""; indent = 2) Original HTML string: <q-test :hello-world="I need $$$"> asap\n or\ntoday <pre>asap\n or\ntoday </pre></q-test> Julia code: quasar(:test, var"hello-world" = R"I need $$$", [ "asap\n or\ntoday", pre( "asap\n or\ntoday " ) ]) Produced HTML: <q-test :hello-world="I need $$$"> asap or today <pre> asap or today </pre> </q-test>
Демонстрации
StippleDemos
Мы создали специальную страницу GitHub с демонстрациями Stipple и множеством доступных для скачивания примеров по следующему адресу: https://github.com/GenieFramework/StippleDemos (Пока не все они обновлены с учетом последних изменений в API. Но большинство должны работать.)
Информационная панель визуализации кредитов для Германии
Полная версия приложения доступна по следующему адресу: https://github.com/GenieFramework/Stipple-Demo-GermanCredits
Информационная панель кластеризации методом k-средних для набора данных Iris Flowers
Полная версия приложения доступна по следующему адресу: https://github.com/GenieFramework/Stipple-Demo-IrisClustering
Дополнительные сведения
Хотя документация Stipple и StippleUI все еще в разработке, в docstring функций можно найти справочные сведения и множество небольших примеров.
help?> btn
search: btn Btn btngroup btndropdown q__btn q__btn__group q__btn__dropdown button Buttons onbutton SubString @onbutton bitstring
Stipple has a component called btn which is a button with a few extra useful features. For instance, it comes in two shapes:
rectangle (default) and round. It also has the material ripple effect baked in (which can be disabled).
The button component also comes with a spinner or loading effect. You would use this for times when app execution may cause a
delay and you want to give the user some feedback about that delay. When used, the button will display a spinning animation as
soon as the user clicks the button.
When not disabled or spinning, btn emits a @click event, as soon as it is clicked or tapped.
Examples
≡≡≡≡≡≡≡≡≡≡
julia> btn("Move Left", color = "primary", icon = "mail", @click("press_btn = true"))
julia> btn("Go to Hello World", color = "red", type = "a", href = "hello", icon = "map", iconright = "send")
julia> btn("Connect to server!", color="green", textcolor="black", @click("btnConnect=!btnConnect"), [
tooltip(contentclass="bg-indigo", contentstyle="font-size: 16px",
style="offset: 10px 10px", "Ports bounded to sockets!")]
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Arguments
≡≡≡≡≡≡≡≡≡≡≡
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1. Behavior
• loading::Bool - Put button into loading state (displays a spinner – can be overridden by using a 'loading'
slot)
• percentage::Union{Int, Float64} - Percentage (0.0 < x < 100.0); To be used along 'loading' prop; Display a
progress bar on the background ex. 23
• darkpercentage::Bool - Progress bar on the background should have dark color; To be used along with
'percentage' and 'loading' props
2. Content
• label::Union{String, Int} - The text that will be shown on the button ex. Button Label
• icon::String - Icon name following Quasar convention; Make sure you have the icon library installed unless
you are using 'img:' prefix; If 'none' (String) is used as value then no icon is rendered (but screen real
estate will still be used for it) ex. map ion-add img:https://cdn.quasar.dev/logo/svg/quasar-logo.svg
img:path/to/some_image.png
• iconright::String - Icon name following Quasar convention; Make sure you have the icon library installed
unless you are using 'img:' prefix; If 'none' (String) is used as value then no icon is rendered (but screen
real estate will still be used for it) ex. map ion-add img:https://cdn.quasar.dev/logo/svg/quasar-logo.svg
img:path/to/some_image.png
• nocaps::Bool - Avoid turning label text into caps (which happens by default)
• nowrap::Bool - Avoid label text wrapping
• align::String - Label or content alignment default. center accepted values. left right center around between
evenly
• stack::Bool - Stack icon and label vertically instead of on same line (like it is by default)
• stretch::Bool - When used on flexbox parent, button will stretch to parent's height
3. General
• type::String - 1) Define the button native type attribute (submit, reset, button) or 2) render component with
<a> tag so you can access events even if disable or 3) Use 'href' prop and specify 'type' as a media tag
default. button ex. a submit reset button image/png href="https://some-site.net" target="_blank"
• tabindex::Union{Int, String} - Tabindex HTML attribute value
4. Navigation
• href::String - Native <a> link href attribute; Has priority over the 'to' and 'replace' props ex.
https://quasar.dev href="https://quasar.dev" target="_blank"
• target::String - Native <a> link target attribute; Use it only with 'to' or 'href' props ex. _blank _self
_parent _top
5. State
• loading::Bool - Put button into loading state (displays a spinner – can be overridden by using a 'loading'
slot)
• padding::String - Apply custom padding (vertical [horizontal]); Size in CSS units, including unit name or
standard size name (none|xs|sm|md|lg|xl); Also removes the min width and height when set
• color::String - Color name for component from the Color Palette (https://quasar.dev/style/color-palette) eg.
primary teal-10
• textcolor::String - Overrides text color (if needed); Color name from the Color Palette
(https://quasar.dev/style/color-palette) eg. primary teal-10
• dense::Bool - Dense mode; occupies less space
• round::Bool - Makes a circle shaped button
Благодарности
StippleUI основан на отличных компонентах Vue Quasar и базовом фреймворке JavaScript Vue.js.