Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[suggestion] An example for unifying the elements format #22

Open
touchft opened this issue May 7, 2021 · 13 comments
Open

[suggestion] An example for unifying the elements format #22

touchft opened this issue May 7, 2021 · 13 comments

Comments

@touchft
Copy link

touchft commented May 7, 2021

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.

  1. 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>

  2. There is only one argument [xxx] allowed in julia element function.
    E.g., Julia ele("aa", [xxx], [yyy],cc="dd") is NOT allowed !.

  3. If there is no argument [xxx], the first string argument in julia element function will be the HTML content.
    E.g., ele("aa", bb="dd", "cc") => HTML <ele bb="dd" cc >aa</ele>

  4. argument :xx will be the HTML attribute v-model="xx"
    E.g., ele(:xx, bb="dd", "cc") => HTML <ele v-model="xx bb="dd">cc</ele>

  5. argument xx=:yy will be the HTML attribute :xx="yy"
    E.g., ele(xx=:yy, bb="dd", "cc") => HTML <ele bb="dd" :xx="yy">cc</ele>

  6. 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 click button the julia bool variable x will be changed to !x

@essenciary
Copy link
Member

essenciary commented May 7, 2021

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

@touchft
Copy link
Author

touchft commented May 7, 2021

Followed wtih above suggestions, I made customized branch of Stipple and StippleUI. The following UI and code are examples.

2

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

1

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

@essenciary
Copy link
Member

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).

@touchft
Copy link
Author

touchft commented May 7, 2021

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

@touchft
Copy link
Author

touchft commented May 7, 2021

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 ^_^

@touchft
Copy link
Author

touchft commented May 7, 2021

Take one more step to simply the suggestions as:

  1. If the last argument is [xxx], [xxx] will always be the HTML content in ele().
    E.g. , Julia ele("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 !.

  1. If the last argument is NOT [xxx], the first string argument in julia element function will be the HTML content.
    E.g., ele("aa", bb="dd", "cc") => HTML <ele bb="dd" cc >aa</ele>

  2. argument :xx will be the HTML attribute v-model="xx"
    E.g., ele(:xx, bb="dd", "cc") => HTML <ele v-model="xx bb="dd">cc</ele>

  3. argument xx=:yy will be the HTML attribute :xx="yy"
    E.g., ele(xx=:yy, bb="dd", "cc") => HTML <ele bb="dd" :xx="yy">cc</ele>

@hhaensel
Copy link
Member

hhaensel commented May 7, 2021

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

@hhaensel
Copy link
Member

hhaensel commented May 7, 2021

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

@hhaensel
Copy link
Member

hhaensel commented May 7, 2021

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

image

@touchft
Copy link
Author

touchft commented May 8, 2021

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

@hhaensel
Copy link
Member

hhaensel commented May 8, 2021

We need to differentiate between elements that are typically wrapped and those that are not.
Furthermore , we should add doc strings somehow

@touchft
Copy link
Author

touchft commented May 8, 2021

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

@hhaensel
Copy link
Member

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 using test_vue_parsing().

If you are ok with this answer, please close the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants