Low-code UI API

StippleUI is a library of reactive UI elements for Stipple.jl a powerful and complete solution for building beautiful, responsive, reactive, high performance interactive data dashboards in pure Julia.

StippleUI provides over 30 UI elements, including forms and form inputs (button, slider, checkbox, radio, toggle, range), lists, data tables, higher level components (badges, banners, cards, dialogs, chips, icons), and layout elements (row, col, dashboard, heading, space) from the Quasar Framework.

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 Components

StippleUI defines functions for most of the Quasar Vue components. Most functions are named like their Quasar counterparts, e.g. btn() for q-btn, tab() for q-tab, etc. Some functions have received different names for clarity, e.g. textfield() for q-input with text type and numberfield() for q-input with number type. Most functions have named positional arguments that are used for definition of common attributes, e.g. label or fieldname. Besides that all functions have a general calling scheme for unnamed arguments and keyword arguments:

  • if a component supports content, the first non-named positional argument is passed as content to the components.
  • any following non-array argument is passed as parameter to the components
  • any non-named array argument is joined and passed as content
  • if a component does not support content, e.g. btn(), all non-named arguments except arrays are passed as parameter. (Passing arrays can be used to define templates for a component. More details can be found in Vue.js docs.)
  • keyword arguments are passed pretty much the same as the Julia expression, except that
    • __ is converted to -
    • ! in the middle of a keyword is converted to .
    • ! at the end of a keyword indicates that the argument should be treated as js expression
    • a Symbol as value of a keyword argument indicates that the argument should be treated as js expression
    • non-standard characters which are not allowed in Julia variable names can be used by employing the var""notation, e.g. span(var"v-html" = "My Text with <br>new lines")
    • most variable names that contain a dash in Vue-syntax are automatically replaced, e.g. "leftlabel" => "left-label". All standard mappings can be found in StippleUI.API.ATTRIBUTES_MAPPINGS

Bindings

There are two ways of assigning component properties

  • assignment of a string value: attribute = "lorem ipsum"
  • assignment of a Javascript expression, by
    • assigning a Symbolattribute = :input
    • or by appending a !-character to the attribute attribute! = "input"
    In most cases this syntax is used to directly bind app variables, but you can also bind more complicated expressions, e.g. array elements (note that js has 0-based indexing): attribute = Symbol("data[0]")attribute = R"data[0]"attribute! = "data[0]" The R"" string macro is a convenient way of defining Symbols.

Javascript code

Vue.js offers the possibility of embedding javascript functions that are called either manually, known as methods. To add one to your app, use the @methods macro as

@methods begin
"""
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
}
"""
end

A method can be called from the Julia code as

@onchange trigger_variable begin
    @run raw"this.logdemo('hello world')"
end

or from another element in the UI as

btn("Call the method", @click("this.function()"))

You can also add code that is triggered automatically when certain events occur, e.g. watch, mounted, created, computed. Such code can easily be defined by the respective macros @watch, @mounted, @created, @computed, e.g.


@created """"
    console.log('This app has just been created!')
"""

See the editable tree demo for more information. These macros also work for explicit models, e.g.

@created MyApp """"
    console.log('This app has just been created!')
"""

User-defined events

There is support for user-defined events by the macro @event.

@event :uploaded begin            
    println("Files have been uploaded!")
end

These lines define julia code that is executed when an event from the client is forwarded to the server. Typically, events at the client originate from certain vue components, e.g. q-uploader. They can be forwarded by calling the @on macro with two Symbol arguments.

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>"

Events can also be triggered manually by calling handle_event(event, 'uploaded') on the client side.

See the file upload demo for more information. Again the @event macro also works for explicit models, e.g.

@event MyApp :uploaded begin            
    println("Files have been uploaded to MyApp!")
end

Missing components

If a quasar component is not yet covered by StippleUI, you can still use the quasar() function to make it available in your UI:

julia> quasar(:btn, label = "Action!") |> println
<q-btn label="Action!"></q-btn>

In a very similar way, you can also integrate vue components or any other html component:

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>"

Using layouts

A layout definition is useful to set a common structure across pages, such as when adding a navigation bar. You can define layouts and pass them to the html function as

function layout()
    #the layout kwarg takes a string, so we join the HTML strings
    join([head(title("Genie app")),
     body([h1("Welcome!"), "<% @yield %>"])])
end
ui() = p("Genie!")

@page("/layout", ui, layout=layout())

Here, @yield injects the page's content into the layout.

Parsing html code to Julia

A very new tool is StippleUIParser. It converts html code to the respective Julian code and prettifies html code. This is meant as a helper tool to port demo code from the internet into Stipple/Genie apps.

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")
      ])
    )
  )
)
Testing parsing result

There is also a testing tool test_vue_parsing() whether the parsing was successful:

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>

which knows the details of binding syntax and which does respect the html preserve tag <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>

Prettify html code

The new prettifier is already used in test_vue_parsing() by default

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>

Genie