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

StippleUI.jl

StippleUI — это библиотека элементов реактивного пользовательского интерфейса для Stipple.jl.

Вместе с

она образует фреймворк Genie, эффективное и полнофункциональное решение для создания красивых, адаптивных, реактивных, высокопроизводительных интерактивных информационных панелей на чистом языке Julia.

StippleUI предоставляет более 30 элементов пользовательского интерфейса, включая формы и элементы ввода (кнопка, ползунок, флажок, переключатель, группа переключателей, диапазон), списки, таблицы данных, компоненты более высокого уровня (значки, баннеры, карточки, диалоговые окна, фишки, значки) и элементы макета (строка, столбец, информационная панель, заголовок, пробел) из фреймворка Quasar.

Новости: Vue 3 и Quasar 2

Начиная с версии 0.24 библиотеки фронтенда StippleUI обновлены до Vue3 и Quasar 2, так как поддержка Vue-2 подошла к концу.

Мы приложили немало усилий, чтобы максимально упростить миграцию. Однако сложные пользовательские интерфейсы могут потребовать небольшой доработки.

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

Дополнительная помощь в миграции

Установка

pkg> add StippleUI

Использование

Компоненты 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>

Оптимизация кода HTML

Новый оптимизатор уже используется в test_vue_parsing() по умолчанию.

julia> prettify("""<div  class="first">single line<div> more\nlines</div></div>"""; indent = 5) |> println
<div class="first">
     single line
     <div>
          more
          lines
     </div>
</div>

Демонстрации

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.