Skip to content

Commit

Permalink
Merge pull request #128 from GenieFramework/hh-vue3
Browse files Browse the repository at this point in the history
update Quasar to version 2
  • Loading branch information
hhaensel authored May 3, 2024
2 parents 5e51062 + e49f302 commit 985079f
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 35 deletions.
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "StippleUI"
uuid = "a3c5d34a-b254-4859-a8fa-b86abb7e84a3"
authors = ["Adrian Salceanu <[email protected]>"]
version = "0.23.3"
version = "0.24.0"

[deps]
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
Expand All @@ -22,10 +22,10 @@ StippleUIDataFramesExt = "DataFrames"
Colors = "0.12"
DataFrames = "1"
Dates = "1.6"
Genie = "5.23.8"
Genie = "5.25.0"
OrderedCollections = "1"
PrecompileTools = "1"
Stipple = "0.28.11"
Stipple = "0.30"
Tables = "1"
julia = "1.6"

Expand Down
48 changes: 45 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,26 @@ beautiful, responsive, reactive, high performance interactive data dashboards in
`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](https://quasar.dev).

**New**: [StippleUIParser](#stippleuiparser)
- conversion of html code to julia code
- pretty-printing of html
## News: Vue 3 / Quasar 2

From version 0.24 on StippleUI has upgraded the front-end libraries to Vue3 / Quasar 2, as Vue-2 has reached its end-of-life.

We have put lots of effort in making migration as easy as possible. Nevertheless, there are some places where advanced user interfaces might need a little tweeking.

## Main Changes for version >= v0.24

### Components
- General: Syncing of additional fields is no longer done with the syntax `fieldname.sync` but rather with `v-model:fieldname`. This is already taken care of by the components' API, e.g. `paginationsync` in `table()`. If you have manually added such fields you need to adapt your code.
- Quasars's Table component has changed the naming of the content attribute from `data` to `rows`.
Accordingly, all references in templates should be changed to `rows` in the long run, e.g.
`table(:mytable, @showif("mytable.data.length"))` should be changed to `table(:mytable, @showif("mytable.data.length"))`.
However, we have introduced a watcher that sets a second field named 'data' to the 'rows' field, which will keep old code running in most cases. The only disadvantage of that solution is that syncing the table content back to the server sends the double amount of data; so that helper might be deprecated in the future.

### More Migration Support
- Stipple's README https://github.com/GenieFramework/Stipple.jl/blob/master/README.md
- Quasar upgrade guide: site: https://quasar.dev/start/upgrade-guide/
- Vue migration guide: https://v3-migration.vuejs.org/

## Installation

```julia
Expand Down Expand Up @@ -95,6 +112,31 @@ julia> span(@click(:mybutton))
"<span v-on:click=\"mybutton = true\"></span>"
```

### Quasar's Flexgrid
Quasar implements a grid system called "Flexgrid" that allows for easy definition of UIs by classes.

A grid can be either vertical (`class = "column"`) or horizontal (`class = "row"`). The child elements receive their size within that container by setting the class `"col"` for equally spaced children, or `"col-6"` for a fixed multiple of 1/12 of the container size (here `6` so 50%).
Moreover, Quasar allows for varying child sizes depending on the size of the container, by adding a size-condition after the col class, e.g. `"col-md-3"`, or simply `"col-md"`.
In the StippleUI-API we define the attributes `col`, `xs`, `sm`, `md`, `lg`, `xl` to make this class definition more convenient, e.g.
```julia
row(htmldiv(col = 2, md = 4)) |> println
# <div class="row"><div class="col-2 col-md-4"></div></div>
```

Furthermore, spacings between child elements are added by setting the class="gutter-md". In case of children in flex containers (`row` or `column`) the setting needs to be `class = "gutter-col-md"`. Again, we have defined an attribute that takes care of the difference and automatically choses the correct setting.
```julia-repl
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>
```

Furthermore, children might badly display if they have a background setting, which is due to the way, Quasar sets margins and padding. The way out is to wrap the children in an extra `div` element, which can conveniently done by using the `@gutter`macro.
```julia-repl
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>
```

More details can be found in the docstrings.

### Javascript code

Vue.js offers the possibility of embedding javascript functions that are called ither manually (`methods`) or automatically when certain events occur, e.g. `watch`, `mounted`, `created`, `computed`. Such code can easily be defined by the respective macros `@methods`, `@watch`, `@mounted`, `@created`, `@computed`, e.g.
Expand Down
1 change: 1 addition & 0 deletions assets/css/quasar.prod.css

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions assets/js/quasar.umd.prod.js

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions ext/StippleUIDataFramesExt.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module StippleUIDataFramesExt

@static if isdefined(Base, :get_extension)
using DataFrames
using StippleUI
using StippleUI.Stipple
end
using StippleUI
using StippleUI.Stipple

isdefined(Base, :get_extension) ? (using DataFrames) : (using ..DataFrames)

function StippleUI.Tables.DataTable()
DataTable(DataFrames.DataFrame())
Expand Down
2 changes: 0 additions & 2 deletions src/API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ const ATTRIBUTES_MAPPINGS = Dict{String,String}(
"optionsselectedclass" => "options-selected-class",
"optionvalue" => "option-value",
"outsidearrows" => "outside-arrows",
"paginationsync" => ":pagination.sync",
"paragraphtag" => "paragraph-tag",
"placeholdersrc" => "placeholder-src",
"popupcontentclass" => "popup-content-class",
Expand All @@ -169,7 +168,6 @@ const ATTRIBUTES_MAPPINGS = Dict{String,String}(
"reversefillmask" => "reverse-fill-mask",
"righticon" => "right-icon",
"rules" => ":rules",
"selected" => ":selected.sync",
"sendraw" => "send-raw",
"separatorclass" => "separator-class",
"separatorstyle" => "separator-style",
Expand Down
8 changes: 4 additions & 4 deletions src/Buttons.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ julia> btn("Connect to server!", color="green", textcolor="black", @click("btnCo
* `round::Bool` - Makes a circle shaped button
"""
function btn( label::Union{String,Symbol} = "",
function btn( label::Union{String,Symbol,Nothing} = nothing,
args...;
content::Union{String,Vector,Function} = "",
kwargs...)
Expand All @@ -84,14 +84,14 @@ function btn( label::Union{String,Symbol} = "",
end

function btn( content::Union{Function,Vector},
label::Union{String,Symbol} = "",
label::Union{String,Symbol,Nothing} = nothing,
args...;
kwargs...)
btn(label, args...; content = content, kwargs...)
end

function btn( args...;
label::Union{String,Symbol} = "",
label::Union{String,Symbol,Nothing} = nothing,
content::Union{String,Vector,Function} = "",
kwargs...)
btn(label, args...; content = content, kwargs...)
Expand All @@ -105,7 +105,7 @@ mutable struct Btn
content
kwargs

Btn(label::Union{String,Symbol} = "",
Btn(label::Union{String,Symbol,Nothing} = nothing,
args...;
content::Union{String,Vector,Function} = "",
kwargs...) = new(fieldname, args, mask, kwargs)
Expand Down
4 changes: 2 additions & 2 deletions src/Chips.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ julia> chip("Add to calendar", icon="event")
* `tabindex::Union{Int, String}` - Tabindex HTML attribute value ex. `0` `100`
3. Model
* `value::Bool` - Model of the component determining if `chip` should be rendered or not default. `true`
* `selected::Bool` - Model for `chip` if it's selected or not NOTE. ".sync" modifier required!
* `selected::Bool` - Model for `chip` if it's selected or not. Needs to be set to a model property.
4. State
* `clickable::Bool` - Is `chip` clickable? If it's the case, then it will add hover effects and emit 'click' events
* `removable::Bool` - Is `chip` removable? If it's the case, then it will add a close button and emit 'remove' events
Expand All @@ -53,7 +53,7 @@ julia> chip("Add to calendar", icon="event")
* `outline::Bool` - Display using the 'outline' design
"""
function chip(args...; kwargs...)
q__chip(args...; kw(kwargs)...)
q__chip(args...; kw(kwargs, Dict("selected" => "v-model:selected"))...)
end

end
6 changes: 3 additions & 3 deletions src/Editors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ julia> StippleUI.form( autocorrect="off", autocapitalize="off", autocomplete="of
----------
# Arguments
----------
]
1. Behaviour
* `fullscreen::Bool` - Fullscreen mode (Note".sync" modifier required!) Example. `:fullscreen.sync="isFullscreen"`
* `fullscreen::Bool` - Fullscreen mode. Example: `fullscreen = :isFullscreen`
* `noroutefullscreenexit::Bool` - Changing route app won't exit fullscreen
* `paragraphtag::String` - Paragraph tag to be used Example. `div`, `p`
2. Content
Expand All @@ -62,7 +62,7 @@ julia> StippleUI.form( autocorrect="off", autocapitalize="off", autocomplete="of
* `contentclass::Union{Dict, Vector, String}` - CSS classes for the input area ex. `my-special-class` `contentclass!="{ 'my-special-class': <condition> }"`
"""
function editor(fieldname::Symbol, args...; kwargs...)
q__editor(args...; kw([:fieldname => fieldname, kwargs...])...)
q__editor(args...; kw([:fieldname => fieldname, kwargs...], Dict("fullscreen" => "v-model:fullscreen"))...)
end

end
21 changes: 14 additions & 7 deletions src/StippleUI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ const assets_config = Genie.Assets.AssetsConfig(package = "StippleUI.jl")

function deps_routes() :: Nothing
if ! Genie.Assets.external_assets(assets_config)
Genie.Router.route(Genie.Assets.asset_route(assets_config, :css, file="quasar.min")) do
Genie.Router.route(Genie.Assets.asset_route(assets_config, :css, file="quasar.prod")) do
Genie.Renderer.WebRenderable(
Genie.Assets.embedded(Genie.Assets.asset_file(cwd=normpath(joinpath(@__DIR__, "..")), type="css", file="quasar.min")),
Genie.Assets.embedded(Genie.Assets.asset_file(cwd=normpath(joinpath(@__DIR__, "..")), type="css", file="quasar.prod")),
:css) |> Genie.Renderer.respond
end
end

if ! Genie.Assets.external_assets(assets_config)
Genie.Router.route(Genie.Assets.asset_route(assets_config, :js, file="quasar.umd.min")) do
Genie.Router.route(Genie.Assets.asset_route(assets_config, :js, file="quasar.umd.prod")) do
Genie.Renderer.WebRenderable(
Genie.Assets.embedded(Genie.Assets.asset_file(cwd=normpath(joinpath(@__DIR__, "..")), type="js", file="quasar.umd.min")),
Genie.Assets.embedded(Genie.Assets.asset_file(cwd=normpath(joinpath(@__DIR__, "..")), type="js", file="quasar.umd.prod")),
:javascript) |> Genie.Renderer.respond
end
end
Expand All @@ -31,18 +31,23 @@ end

function theme() :: Vector{String}
[
Stipple.Elements.stylesheet(Genie.Assets.asset_path(assets_config, :css, file="quasar.min"))
Stipple.Elements.stylesheet(Genie.Assets.asset_path(assets_config, :css, file="quasar.prod"))
]
end

#===#

function deps() :: Vector{String}
[
Genie.Renderer.Html.script(src="$(Genie.Assets.asset_path(assets_config, :js, file="quasar.umd.min"))")
Genie.Renderer.Html.script(src="$(Genie.Assets.asset_path(assets_config, :js, file="quasar.umd.prod"))")
]
end

function plugins() :: Vector{String}
[
"Quasar"
]
end
#===#

include("API.jl")
Expand Down Expand Up @@ -170,12 +175,14 @@ end
function __init__()
deps_routes()
push!(Stipple.Layout.THEMES[], theme)
Stipple.add_css(theme)
Stipple.deps!(@__MODULE__, deps)
Stipple.add_plugins(StippleUI, plugins)

@static if !isdefined(Base, :get_extension)
@require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" begin
# evaluate the code of the extension without the surrounding module
include(joinpath(@__DIR__, "..", "ext", "StippleUIDataFrames.jl"))
include(joinpath(@__DIR__, "..", "ext", "StippleUIDataFramesExt.jl"))
end
end
end
Expand Down
37 changes: 31 additions & 6 deletions src/Tables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export cell_template, qtd, qtr
register_normal_element("q__table", context = @__MODULE__)

const ID = "__id"
const DATAKEY = "data" # has to be changed to `rows` for Quasar 2
const DATAKEY = "rows"
const DataTableSelection = Vector{Dict{String, Any}}

struct2dict(s::T) where T = Dict{Symbol, Any}(zip(fieldnames(T), getfield.(Ref(s), fieldnames(T))))
Expand Down Expand Up @@ -210,10 +210,10 @@ function rows(t::T)::Vector{OrderedDict{String,Any}} where {T<:DataTable}
rows
end

function data(t::T; datakey = "data", columnskey = "columns")::Dict{String,Any} where {T<:DataTable}
function data(t::T; datakey = DATAKEY, columnskey = "columns")::Dict{String,Any} where {T<:DataTable}
OrderedDict(
columnskey => columns(t),
datakey => rows(t)
datakey => rows(t),
)
end

Expand Down Expand Up @@ -478,6 +478,8 @@ function table( fieldname::Symbol,
datakey::String = "$fieldname.$DATAKEY",
columnskey::String = "$fieldname.columns",
filter::Union{Symbol,String,Nothing} = nothing,
selected::Union{Symbol,String,Nothing} = nothing,
pagination::Union{Symbol,String,Nothing} = nothing,
paginationsync::Union{Symbol,String,Nothing} = nothing,

columns::Union{Nothing,Bool,Integer,AbstractString,Vector{<:AbstractString},Vector{<:Integer}} = nothing,
Expand Down Expand Up @@ -526,12 +528,14 @@ function table( fieldname::Symbol,

q__table(args...;
kw([
Symbol(":data") => "$datakey",
Symbol(":", DATAKEY) => "$datakey",
Symbol(":columns") => "$columnskey",
Symbol("row-key") => rowkey,
:fieldname => fieldname,
(filter === nothing ? [] : [:filter => filter])...,
(paginationsync === nothing ? [] : [:paginationsync => paginationsync])...,
:filter => filter,
selected === nothing ? (:selected => nothing) : (Symbol("v-model:selected") => selected),
:pagination => pagination,
paginationsync === nothing ? (:paginationsync => nothing) : (Symbol("v-model:paginationsync") => paginationsync),
kwargs...
])...
)
Expand Down Expand Up @@ -578,6 +582,27 @@ function Stipple.render(dtp::DataTablePagination)
response
end

# function to autogenerate entries for js_mounted to make Tables from Quasar1 compatible with tables from Quasar2
# Background: the field 'data' has been renamed to 'rows' in Quasar 2
# This function autogenerates entries that set the 'data' field of tables to the 'rows' field. As Vue3's mechanism
# for watchers relies on getter and setter functions any get or set operation on 'data' will be reflected in rows
# and the respective watchers will be triggered.
function Stipple.js_created_auto(::M) where M<:ReactiveModel
io = IOBuffer()
for (fieldname, fieldtype) in zip(fieldnames(M), fieldtypes(M))
if fieldtype <: DataTable || fieldtype <: Reactive{<:DataTable}
print(io, "\nthis.$fieldname.data = this.$fieldname.rows")
end
end
String(take!(io))
end

function Stipple.js_watch_auto(::M) where M<:ReactiveModel
[fieldname => "function() {this.$fieldname.data = this.$fieldname.rows}"
for (fieldname, fieldtype) in zip(fieldnames(M), fieldtypes(M))
if fieldtype <: DataTable || fieldtype <: Reactive{<:DataTable}
]
end
#===#

function Stipple.watch(vue_app_name::String, fieldtype::R{T}, fieldname::Symbol, channel::String, model::M)::String where {M<:ReactiveModel,T<:DataTable}
Expand Down

2 comments on commit 985079f

@hhaensel
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/106110

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.24.0 -m "<description of version>" 985079fd27936e04e0b3bf2027acd7a4f87b4992
git push origin v0.24.0

Please sign in to comment.