-
Notifications
You must be signed in to change notification settings - Fork 15
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
update section of page(component) based on reactive property change #84
Comments
Your thinking the wrong way 😉 using Stipple
using StippleUI
@vars TimeSeries begin
button_pressed = false
titles = ["Title A", "Title B"]
private = "private", PRIVATE
readonly = "readonly", READONLY
end
function ui(model)
Stipple.onbutton(model.button_pressed) do
@show "Button Pressed"
model.titles[] = ["Title C", "Title D"]
end
page(
model,
class = "container",
[
btn("Update Timeline", @click("button_pressed = true"))
timeline("",color = "primary", [
timelineentry("Timeline", heading=true)
timelineentry("", @recur("t in titles"), title = :t)
])
],
)
end
model = Stipple.init(TimeSeries)
route("/") do
html(ui(model), context = @__MODULE__)
end Note the new definition syntax for reactive models, which we introduced tonight with Stipple v0.25.14. |
Oh yes thanks @hhaensel I didn't pay attention to that line
|
I need to find some free time to document stipple code |
@hhaensel I really like this |
READONLY is there rigjt from the beginning. It means that fields from the model are pushed to the client but the client can't update the fields back to the server. PRIVATE fields don't appear on the client side. |
never used it. I can vaguely recall I think Adrian contributed it few months back. Regardless I need to start documenting Stipple 📝😔 |
Thank you very much for that. I'm still trying to build a proper mental model of what is going on and trying to find the similarity with GenieFramework/Stipple.jl#147 I noticed that you said it was important in that other issue to have something like: Stipple.on(model.isready) do ready
ready || return
push!(model)
end which is missing here. From the previous post, my impression is that one must also have something like Stipple.notify(model.titles) inside the button press handler after updating the titles (but I don't see that as part of your solution here either). Must the data structure I loop over with model.titles::R{Vector{DataFrameRow}} = copy(eachrow(DataFrame(Message = ["Title A", "Title B", "Title C"]))) and then try to use timelineentry("", title = Symbol("item.Message"), @recur("(item, index) in titles")) but this does not work. I end up with a timeline of three entries but no title text for the entries. |
Part I - Updating values:There are 3 main possibilities of sending updated values to the front end (or client):
Part II - isready handler
Part III -
|
@essenciary |
Also spotted another no-go: The handler should not go into the My final version would look like this: using Stipple
using StippleUI
using DataFrames
@vars TimeSeries begin
button_pressed = false
df = DataFrame(:Message => ["Title A", "Title B"]), READONLY
private = "private", PRIVATE
nonreactive = "nr", Stipple.NON_REACTIVE
end
function ui(model)
page(
model,
class = "container",
row(cell(class = "st-module", [
btn("Update Timeline", color = "primary", @click("button_pressed = true"))
timeline("", color = "primary", [
timelineentry("Timeline", heading=true),
timelineentry("", @recur("t in df.columns[df.colindex.lookup.Message - 1]"), title = :t)
])
])),
)
end
function handlers(model)
Stipple.onbutton(model.button_pressed) do
@show "Button Pressed"
model.df[] = DataFrame(:Message => ["Title C", "Title D", "Title E"])
end
model
end
model = Stipple.init(TimeSeries) |> handlers
route("/") do
html(ui(model), context = @__MODULE__)
end
up() |
@essenciary We'd rather build a |
Let's reopen this, because the current situation of rendering dataframes is unsatisfying. |
I've been looking over this and I don't understand the value of an abstract renderer for DataFrames. Surely, the JSON output needs to be structured in a way that matches whatever the specific UI component expects. |
I propose to add the following lines to @require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" function render(df::DataFrames.DataFrame, fieldname::Union{Nothing, Symbol} = nothing)
Dict(zip(names(df), eachcol(df)))
end This will render dataframes as a js dictionary (as would Withh this modification timelineentry("", @recur("t in df.columns[df.colindex.lookup.Message - 1]"), title = :t) would become timelineentry("", @recur("t in df.Message"), title = :t) |
So the benefit is that you can manage your data as a dataframe and can use |
Again, I don't see the value of this as part of the core. It can be easily implemented by hand. That's why we have Also, I wanted to discuss the use of |
Finally, we should probably not care about |
Adding to this, |
Agree 100% |
We can easily write a renderer for the Stipple.render(table::T, fieldname) where T = OrderedDict(zip(Tables.columnnames(df), Tables.columns(df))) But I wonder, how we'd define the types T that support the |
I've not really understood why you decided to define a fieldname-dependent render function function Stipple.render(t::T, fieldname::Union{Symbol,Nothing} = nothing) where {T<:DataTable}
data(t, fieldname)
end
function data(t::T, fieldname::Symbol; datakey = "data_$fieldname", columnskey = "columns_$fieldname")::Dict{String,Any} where {T<:DataTable}
Dict(
columnskey => columns(t),
datakey => rows(t)
)
end so that julia> df = DataFrame(:a => 1:3, :b => 2:4)
3×2 DataFrame
Row │ a b
│ Int64 Int64
─────┼──────────────
1 │ 1 2
2 │ 2 3
3 │ 3 4
julia> render(DataTable(df), :test)
Dict{String, Any} with 2 entries:
"columns_test" => Column[Column("a", false, "a", :left, "a", true), Column("b", false, "b", :left, "b", true)]
"data_test" => Dict{String, Any}[Dict("__id"=>1, "b"=>2, "a"=>1), Dict("__id"=>2, "b"=>3, "a"=>2), Dict("__id"=>3, "b"=>4, "a"=>3)] I think that's the only place where we have fieldname-dependent rendering, right? |
@zygmuntszpak Stipple.render(df::DataFrame, fieldname::Union{Nothing, Symbol} = nothing) = Dict(zip(names(df), eachcol(df))) yourself and go with timelineentry("", @recur("t in df.Message"), title = :t) in th |
Thank you very much for all the explanations and suggestions. In the meantime, I had gone with something like this (I hadn't made the switch to @vars yet) : @reactive mutable struct TimeSeries begin
main_table::R{DataTable} = DataTable(load_dataframe(), table_options)
timeline_entries::R{Dict{String, Vector{String}}} = initialise_timeline_entries(main_table)
end function initialise_timeline_entries(table)
df = DataFrames.transform(table.data, :Message, :Date, :X => (ByRow(x-> x > 0 ? "left" : "right") )=> :Side, :X => (ByRow(x->abs(x))) => :Score)
sort!(df, :Score, rev = true)
df₂ = DataFrames.select(df, :Message, :Date, :Side)
dict = Dict(zip(names(df₂), eachcol(df₂)))
return Reactive(dict) timelineentry("", title = Symbol("timeline_entries.Message[index]"), subtitle = Symbol("timeline_entries.Date[index]"), side = Symbol("timeline_entries.Side[index]"), @recur("(message, index) in timeline_entries.Message")) If I understood correctly, then what you are suggesting is that I can define Stipple.render(df::DataFrames.DataFrame, fieldname::Union{Nothing, Symbol} = nothing) = Dict(zip(names(df), eachcol(df))) to take care of converting to the dictionary which yields a convenient syntax to iterate over, i.e. @recur("(message, index) in timeline_entries.Message")) |
That's correct except that df should be of type DataFrame not DataTable |
I'm not sure the So users should implement their own type that renders a DataFrame without type piracy. |
Regarding the plug-in system, is the suggestion then to develop a In that case wouldn't your definition of type piracy be violated in that package as well (defining a method that dispatches on a type that is not defined by the package that defines the method)? I came across the following definition of type piracy:
If I understood that definition, part of the issue is who owns |
If you are the autor of the core package, it is ok to do this, see https://discourse.julialang.org/t/how-bad-is-type-piracy-actually/37913/9 |
In the case of dataframes it is even clear how a default json rendering should look like. JSON.jl implements it exactly in that way so that df.colname works on both, server side and client side. This is what I would definitely expect. The different rendering by JSON3.jl comes from the fact that for unknown types JSON3 renders the fields of the type and not the properties. |
@essenciary |
@hhaensel Extension makes sense. |
I mean julia < v1.9 |
Ah bummer... Honestly I'd prefer bumping the Julia compat to 1.9 -- but this will be too steep of a jump I think. In this case we have to support Julia 1.6/1.8 (technically 1.8 is now the LTS) and remove all the Require code when we reach Julia 1.10 (as 1.9 will become LTS so we can bump to 1.9 Julia compat then). With this approach it would make sense to have a clean implementation. Ex putting the 1.9 extension implementation in a file and the 1.8 Require implementation in a different file. And have some sort of static include to leverage precompilation (if possible). Then at 1.10 we just remove the "dead code". |
Is this issue solved now? With the latest PR we have moved DataFrames to an extension and any type that supports the Tables API is now rendered as OrderedDict, e.g. julia> df = DataFrame(:a => [1, 2, 3], :b => ["a", "b", "c"])
3×2 DataFrame
Row │ a b
│ Int64 String
─────┼───────────────
1 │ 1 a
2 │ 2 b
3 │ 3 c
julia> render(df)
OrderedDict{String, AbstractVector} with 2 entries:
"a" => [1, 2, 3]
"b" => ["a", "b", "c"] |
Just to update this issue:
@essenciary , @AbhimanyuAryan , @zygmuntszpak Actually, I think this issue can now be closed, as all the questions and proposals in this slightly diverging issue have been addressed. |
@hhaensel this can be closed as the original question was about looping and dynamic rendering |
reported by @zygmuntszpak
I have created a timeline, and I would like to update the timeline entries in response to a user selection. How do I ensure that the UI gets updated? My UI references a reactive component, but after updating the reactive component, the UI doesn't automatically update. Please see the following MWE:
The text was updated successfully, but these errors were encountered: