StippleUI.jl
StippleUI.jl copied to clipboard
[suggestion] An example for unifying the elements format
I hope to use same format for all elements to make things simpler. I hope/suggest that the following format rules are valid for all julia element functions. Special element functions may have other format rules.
-
The argument
[xxx]will always be theHTML contentno matter its position inele(). E.g. , Juliaele("aa", "bb", [xxx], cc="dd")=> HTML<ele aa bb cc="dd" >xxx</ele> -
There is only one argument
[xxx]allowed in julia element function. E.g., Juliaele("aa", [xxx], [yyy],cc="dd")is NOT allowed !. -
If there is no argument
[xxx], the first string argument in julia element function will be theHTML content. E.g.,ele("aa", bb="dd", "cc")=> HTML<ele bb="dd" cc >aa</ele> -
argument
:xxwill be theHTML attribute v-model="xx"E.g.,ele(:xx, bb="dd", "cc")=> HTML<ele v-model="xx bb="dd">cc</ele> -
argument
xx=:yywill be theHTML attribute :xx="yy"E.g.,ele(xx=:yy, bb="dd", "cc")=> HTML<ele bb="dd" :xx="yy">cc</ele> -
other vue/custom attribute can be introduced by macro/string E.g., julia
btn("button",(@vclick "x=!x"),[])=> HTML<template><q-btn @click=\"x=!x\" label=\"button\"></q-btn></template>where, when clickbuttonthe julia bool variablexwill be changed to!x
@touchft I can't see any good arguments for doing this. I guess we'd have to look at each of the points, some ideas might bring improvements, but let's talk about the first. How would you implement
The argument [xxx] will always be the HTML content no matter its position in ele().
E.g. , Julia ele("aa", "bb", [xxx], cc="dd") => HTML <ele aa bb cc="dd" >xxx</ele>
The way I see it you'd have to look at all the arguments, search for an array, check if there is more than one array (per your number 2), if there is I guess throw an exception (?), then set the array as the content of the element. If there is no array, take the 1st string (so check the types of all the arguments and keep a reference to the 1st string or iterate over the arguments again -- then I presume throw another exception if there is no string argument).
Please explain the benefits of doing this.
In my opinion all these things introduce a huge amount of computations (think a large HTML document) and cognitive load for the developer/maintainer (by making the code more complicated).
Update 1:
I mean, all the above when we can do:
julia> span([
h1("hello")
p("I'm dude")
], a, b)
"<span a b><h1>hello</h1><p>I'm dude</p></span>"
Update 2
You can also do:
julia> span(a=true, b=true, [
h1("hello")
p("I'm dude")
])
"<span a b><h1>hello</h1><p>I'm dude</p></span>"
Followed wtih above suggestions, I made customized branch of Stipple and StippleUI. The following UI and code are examples.

When mouse hover the blue button UPDATE, the tips will show (see following pic).

The above UI is coded with customized branch of Stipple/StippleUI as following :
cell(style="max-width: 685px",[
# online: uart Updateing card
div((@var class hiddenOffline),[
card(bordered=true, [
row([
card_section(style="height:0px;", class="text-h4",["1. Data Source/Save"])
tabs(:datatab, dense=true, class="text-teal", inlinelabel = "", [
tab("Data Ports", name="Data Ports", icon="mail")
tab("Save Data", name="Save Data", icon="mail")
])
])
separator()
panels(:datatab,style="height: 80px;",animated=true, [
panel(name="Data Ports",[
row([
textfield("UART COM", :uartCfg, placeholder="USB0",outlined="", filled="",[])
textfield("DATA COM", :uartData, placeholder="USB1",[])
btn("Update",color="blue",textcolor="black",(@vclick uartUpdate=!uartUpdate),[
tooltip(contentclass="bg-indigo",contentstyle="font-size: 16px",(@is offset [10, 10]), ["Specify device ports and then click"])
])
btn("Connect",color="red",textcolor="black",(@var class hiddenConnect),loading=:bindprogress, (@vclick "btnConnect=!btnConnect"),[
tooltip(contentclass="bg-indigo",contentstyle="font-size: 16px",(@is offset [10, 10]), ["Bind uart ports to sockets"])
])
btn("Connected!",color="green",textcolor="black",(@var class hiddenConnected),loading=:bindprogress,(@vclick "btnConnect=!btnConnect"),[
tooltip(contentclass="bg-indigo",contentstyle="font-size: 16px",(@is offset [10, 10]), ["Uart ports bounded to sockets!"])
])
])
])
panel(name="Save Data",[
row([
textfield("Choose path/folder to save files", :path, placeholder="/data",outlined="", filled="",[])
checkbox(label="record CFG", color="teal", :cfg2file)
checkbox(label="record Data", color="orange", :data2file)
])
])
])
])
])
])
Note again: the above code use customized branch of Stipple/StippleUI
The UI looks really cool! ❤
However, my comments remain. As I said, points 1, 2 and 3 introduce massive amounts of computations and complexity (for potential benefits that are yet unclear to me) while for 4, 5 and 6 we already have APIs for doing these things (which maybe are unclear due to the lack of docs, so defo the docs are needed urgently).
In most cases, the [xxx] will appear in the last position, so we can combine elements with many fold/level elegantly. Otherwise, it will make user harder to locate the element's attributes and case the problem
"head heavy foot light !"
The UI looks really cool! heart
However, my comments remain. As I said, points 1, 2 and 3 introduce massive amounts of computations and complexity (for potential benefits that are yet unclear to me) while for 4, 5 and 6 we already have APIs for doing these things (which maybe are unclear due to the lack of docs, so defo the docs are needed urgently).
Let's take aside of computation amount, one of benefits from the suggestions is : No need to learn various element api format ^_^
Take one more step to simply the suggestions as:
- If the last argument is
[xxx],[xxx]will always be theHTML contentinele(). E.g. , Juliaele("aa", "bb", cc="dd", [xxx])=> HTML<ele aa bb cc="dd" >xxx</ele>
~~2. There is only one argument [xxx] allowed in julia element function.
E.g., Julia ele("aa", [xxx], [yyy],cc="dd") is NOT allowed !.~~
-
If the last argument is NOT
[xxx], the first string argument in julia element function will be theHTML content. E.g.,ele("aa", bb="dd", "cc")=> HTML<ele bb="dd" cc >aa</ele> -
argument
:xxwill be theHTML attribute v-model="xx"E.g.,ele(:xx, bb="dd", "cc")=> HTML<ele v-model="xx bb="dd">cc</ele> -
argument
xx=:yywill be theHTML attribute :xx="yy"E.g.,ele(xx=:yy, bb="dd", "cc")=> HTML<ele bb="dd" :xx="yy">cc</ele>
We have already the kw syntax children=xxx for positional independence. You have to supply an empty string as first argument, if you don't want to use it, though
EDIT: I didn't have time to check. The keyword we chose is inner. Currently it doesn't handle arrays correctly, so you have to do inner = join(["aa", "bb"])
I submitted a PR in Genie to support arrays, but I kept the keyword inner for now.
I changed StippleUI to support xelem(:myelem, args..., kwargs...) with and without prefix, so it is easy to define own elements:
mydiv(args...; kwargs...) = xelem_pure(:div, args...; kwargs...)
hh(elem::Symbol, args...; kwargs...) = xelem(:elem, :hh, args...; kwargs...)
to allow for
julia> mydiv("hello", class=:hidden)
"<div :class=\"hidden\">hello</div>"
julia> hh(:elem, "test", :label)
"<template><hh-elem label>test</hh-elem></template>"
Cool interface, indeed!
This is, how I would rewrite it with the current StippleUI
tooltip(args...; kwargs...) = quasar(:tooltip, args...; kwargs...)
tabs(fieldname::Symbol, args...; kwargs...) = quasar(:tabs, args...; fieldname=fieldname, kwargs...)
tab(args...; kwargs...) = quasar_pure(:tab, args...; kwargs...)
panels(fieldname::Symbol, args...; kwargs...) = quasar(:tab__panels, args...; fieldname=fieldname, kwargs...)
panel(args...; kwargs...) = quasar_pure(:tab__panel, args...; kwargs...)
mydiv(args...; kwargs...) = Stippleui.API.xelem(:div, args...; wrap = StippleUI.NO_WRAPPER, kwargs...)
# with current master you could also do
# mydiv(args...; kwargs...) = xelem_pure(:div, args...; kwargs...)
function ui()
page(vm(model), class="container", title="Hello Stipple",
cell(style="max-width: 685px",[
# online: uart Updateing card
mydiv(class=:hiddenOffline,[
card(bordered=true, [
row([
card_section(style="height:0px;", class="text-h4",["1. Data Source/Save"])
tabs(:datatab, dense=true, class="text-teal", inline__label = "", [
tab("Data Ports", name="Data Ports", icon="cable")
tab("Save Data", name="Save Data", icon="save")
])
])
separator()
panels(:datatab, style="height: 80px;",animated=true, [
panel(name="Data Ports",[
row([
textfield("UART COM", :uartCfg, placeholder="USB0",outlined="", filled="")
textfield("DATA COM", :uartData, placeholder="USB1")
btn("Update", color="blue", textcolor="black", @click("uartUpdate=!uartUpdate"),
inner = tooltip(
contentclass="bg-indigo",
contentstyle="font-size: 16px", style="offset: 10px 10px",
"Specify device ports and then click")
)
btn("Connect", color="red", textcolor="black", class = :hiddenConnect, loading=:bindprogress,
@click("btnConnect=!btnConnect"),
inner = tooltip(
contentclass="bg-indigo", contentstyle="font-size: 16px",
style="offset: 10px 10px",
"Bind uart ports to sockets")
)
btn("Connected!", color="green", textcolor="black", class= :hiddenConnected, loading=:bindprogress,
@click("btnConnect=!btnConnect"),
inner = quasar(:tooltip,
contentclass="bg-indigo",
contentstyle="font-size: 16px",
style="offset: 10px 10px",
"Uart ports bounded to sockets!")
)
])
])
panel(name="Save Data",[
row([
textfield("Choose path/folder to save files", :path, placeholder="/data",outlined="", filled="")
checkbox("record CFG", color="teal", :cfg2file)
checkbox("record Data", color="orange", :data2file)
])
])
])
])
])
])
)
end
Note the different positions of the brackets for the macros, which is more Julian style.
I only guessed, what your macro @is does, so I replaced it by style = "offset: 10px, 10px;".

Cool interface, indeed!
This is, how I would rewrite it with the current StippleUI
tooltip(args...; kwargs...) = quasar(:tooltip, args...; kwargs...) tabs(fieldname::Symbol, args...; kwargs...) = quasar(:tabs, args...; fieldname=fieldname, kwargs...) tab(args...; kwargs...) = quasar_pure(:tab, args...; kwargs...) panels(fieldname::Symbol, args...; kwargs...) = quasar(:tab__panels, args...; fieldname=fieldname, kwargs...) panel(args...; kwargs...) = quasar_pure(:tab__panel, args...; kwargs...) mydiv(args...; kwargs...) = Stippleui.API.xelem(:div, args...; wrap = StippleUI.NO_WRAPPER, kwargs...) # with current master you could also do # mydiv(args...; kwargs...) = xelem_pure(:div, args...; kwargs...) function ui() page(vm(model), class="container", title="Hello Stipple", cell(style="max-width: 685px",[ ... ... ... ]) ) end
Great! I see that you add multiple quasar elements manually. How about add most of them by registering in the same way as Genie.renderers.Html do and leave some special elements manually handled. Some examples are followed:
module UIElements
using ..API
using ..StippleUI:DEFAULT_WRAPPER
using ..StippleUI.API:ATTRIBUTES_MAPPINGS
import Genie.Renderer.Html: HTMLString, normal_element, register_normal_element
# export
const UI_ELEMENTS = [
:btngroup, :card, :drawer, :icon, :tab, :select,
:checkbox, :badge, :elements, :image, :tabs, :panel,
:chip, :layout, :separator,:page, :item, :textfield,
:form, :list, :space, :tooltip,:panels, :toggle,
:uploader, :btn, :dialog, :radio, :video, :filefield,
:slider
]
const UI_ELEMENTS_EXT = [
:card_section, :card_sections, :page_container, :item_section,:tab_panel, :tab_panels, :item_label
]
const UI_ELEMENTS_MAP = Dict{Symbol,Symbol}(
:image => :img,
:space => :select,
:panel => :tab_panel,
:panels => :tab_panels,
:textfield=> :input,
:filefield=> :file
)
const NON_EXPORTED_UI = []
# change ':x_y' -> ':x__y'
function qname(elem::Union{Symbol,String})
if elem in keys(UI_ELEMENTS_MAP)
elem = UI_ELEMENTS_MAP[elem]
end
m = match(r"(.*)_(.*)", "$elem")
if m !== nothing
qelem = Symbol(m.captures[1] * "__" * m.captures[2])
else
qelem = elem
end
return elem, qelem
end
# register a new UI element method which is a wrap of old one
"""
register_UI_element(elem::Union{Symbol,String}; context::Module = @__MODULE__) :: Nothing
Generates a new Julia function representing an UI element.
"""
function register_UI_element(elem::Union{Symbol,String}; context::Module = @__MODULE__) :: Nothing
newelem, qelem = qname(elem)
Core.eval(context, """
function $elem(args...; wrap::Function = DEFAULT_WRAPPER, kwargs...)
content, symbols, values, strings, arrays, others, kwargs = argsfilter(args...; kwargs...)
if isempty(strings)
attr = [symbols...,values..., kwargs...]
elseif :label in keys(kwargs)
attr = [symbols...,values..., kwargs...]
else
label = strings[1]
strings=strings[2:end]
if label == ""
attr = [symbols..., values..., kwargs...]
else
attr = [:label=>label, symbols..., values..., kwargs...]
end
end
wrap() do
q__$qelem( content, strings..., others..., arrays...;
attributes(attr, ATTRIBUTES_MAPPINGS)...)
end
end
""" |> Meta.parse)
elem in NON_EXPORTED_UI || Core.eval(context, "export $elem" |> Meta.parse)
nothing
end
"""
register_elements() :: Nothing
Generated functions that represent Julia functions definitions corresponding to HTML elements.
"""
function register_elements(; context = @__MODULE__) :: Nothing
UI_ELEMENTS_FULL = [UI_ELEMENTS..., UI_ELEMENTS_EXT...]
for elem in UI_ELEMENTS_FULL
newelem, qelem = qname(elem)
qelemname = "q__" * string(qelem)
((newelem == elem) || !(newelem in UI_ELEMENTS_FULL)) && register_normal_element(qelemname, context = @__MODULE__)
register_UI_element(elem)
end
nothing
end
#===#
register_elements()
end # model
With the unifying most of UI Elements, some special element are manually handled separately (i.e., "Banner.jl", "BigNumber.jl", etc). The modified StippleUI.jl is attached here.
module StippleUI
...
#===#
include("API.jl")
########### common Elements ##################
include("UIElements.jl")
## === #
# include("Badge.jl")
include("Banner.jl")
include("BigNumber.jl")
# include("Button.jl")
# include("Card.jl")
# include("Checkbox.jl")
# include("Chip.jl")
include("Dashboard.jl")
# include("Dialog.jl")
# include("Form.jl")
# include("FormInput.jl")
include("Heading.jl")
# include("Icon.jl")
# include("List.jl")
# include("Radio.jl")
include("Range.jl")
# include("Select.jl")
# include("Separator.jl")
# include("Space.jl")
include("Table.jl")
# include("Toggle.jl")
############# new Elements ##################
########### common Elements ##################
@reexport using .UIElements
#===#
# @reexport using .Badge
@reexport using .Banner
@reexport using .BigNumber
# @reexport using .Button
# @reexport using .Card
# @reexport using .Checkbox
# @reexport using .Chip
@reexport using .Dashboard
# @reexport using .Dialog
# @reexport using .Form
# @reexport using .FormInput
@reexport using .Heading
# @reexport using .Icon
# @reexport using .List
# @reexport using .Radio
@reexport using .Range
# @reexport using .Select
# @reexport using .Separator
# @reexport using .Space
@reexport using .Table
# @reexport using .Toggle
#===#
...
end # module
We need to differentiate between elements that are typically wrapped and those that are not. Furthermore , we should add doc strings somehow
We need to differentiate between elements that are typically wrapped and those that are not. Furthermore , we should add doc strings somehow
Hope to see the change.
Thanks
Lot's of things have change since then.
- most of the quasar components are added including docstrings
- non-supported quasar components can be added by
quasar(), e.g.quasar(:badge, color = "primary", "mytext") parse_vue_html()to translate html code into the respective Julia code. (Fails only in some rare cases, which can easily be corrected). You can even test whether the Julia code reproduces proper html by usingtest_vue_parsing().
If you are ok with this answer, please close the issue.