Allow to connect! app variable to an observable
yakir12 opened this issue · comments
[this is from a discussion on Discord]
Is there a way to Observable.connect!
a variable from an @app
body to an Observable
?
Something like this:
using Observables, MyModule
@app begin
@in variable = 0
end
connect!(MyModule.some_observable, variable)
Right now I do this:
@onchange variable begin
MyModule.some_observable[] = variable
end
I've tried using connect!
between two reactive variables but it's not working; don't know if it'd work with an observable from the outside
using GenieFramework, Observables
@genietools
@app begin
@in var1 = 0
@in var3 = 0
@onchange isready begin
model = @init
Observables.connect!(model.var1, model.var3)
end
@onchange var1 begin
println("var1 $var1")
end
@onchange var3 begin
println("var3 $var3")
end
end
ui() = slider(1:1:10, :var1)
@page("/", ui)
Another possibility is to use notify
like
using Observables, MyModule
@app begin
@in variable = 0
@onchange variable begin
notify(some_observable)
end
end
This works between @in, @out
variables
The question is, what are you going to achieve. You have to be aware that every refresh of the page in the browser creates a new model and upon creation of that model the model's variable needs to be connected to your Observable. That's nicely done by your
@onchange variable begin
MyModule.some_observable[] = variable
end
Could you precisely describe what scenario you have in mind?
Maybe we find a solution then.
Otherwise, if you are not considering multi-user applications, you could work with a global model
model = @init
and connect the model's variable directly.
connect!(your_observable, model.variable)
But you have to write your own route function in that case.
This highlights how I'm "abusing" the Genie framework to my specific needs...
Yes, I have (by definition) only one user at a time, and cannot have multiple instances of the model. My use-case is a UI for controlling hardware: A user controls parameters used to control an LED strip and a Raspberry Pi camera (in a behavioral experiment with dung beetles). The RPI is connected to the user's laptop with an Ethernet cable and the RPI serves the generated website to the client. I love the fact that the user doesn't need to install anything to control the whole thing, just open a browser and navigate to a specific IP address.
Refreshing the page doesn't seem to pose any errors as of yet, perhaps because of how I set it up (you can see what it looks like for now here: https://github.com/yakir12/dancingqueen/tree/main/app).
In this case I think it is absolutely ok to work with a global model.
My current working horse for such apps is:
using GenieFramework
@genietools
@app begin
@in x = "Enter to return"
@out y = 0
@onchange isready push!
end
model = @init
UI = Ref{Any}()
UI[] = [
row(cell(class = "st-module", [
h3("Input")
row(textfield(class = "col-6", "Text Label", :x))
row(numberfield(class = "col-6", "Number Label", :y))
]))
row(cell(class = "st-module", [
h3("Output")
row("X: {{ x }}")
row("Y: {{ y }}")
]))
]
ui() = UI[]
route("/") do
global model
page(model, ui) |> html
end
up(open_browser = true)
When you need faster responsiveness, consider using named apps and make the following changes:
# in this example I name the handlers function "myhandlers"
# if you don't specify the name it will be called 'handlers'
@app MyApp begin
@in x = "Enter to return"
@out y = 0
@onchange isready push!
end myhandlers
model = init(MyApp, debounce = 0) |> myhandlers
you can see what it looks like for now here: https://github.com/yakir12/dancingqueen/tree/main/app
This has some similarity to my CameraWidget.jl Maybe you find some inspiration there how I managed the on/off problem.
Just to comment on your solution
@onchange variable begin
MyModule.some_observable[] = variable
end
sounds perfectly fine for me. It won't use a different mechanism for triggering than connect!()
does. Have a look at the following snippet:
julia> o1 = Observable(1);
julia> o2 = Observable(2);
julia> connect!(o1, o2)
ObserverFunction defined at C:\Users\helmu\.julia\packages\Observables\PHGQ8\src\Observables.jl:539 operating on Observable(2)
julia> Observables.listeners(o1)
Pair{Int64, Any}[]
julia> Observables.listeners(o2)
1-element Vector{Pair{Int64, Any}}:
0 => Observables.var"#11#12"{Observable{Int64}}(Observable(2))
So calling connect!()
does nothing else than creating a listener that updates o1
with the value of o2
on any update or notification of o2
. That's also what you do with your manual solution which you could write in a shorter form:
@onchange variable MyModule.some_observable[] = variable
But you could also do
model = init(MyApp, debounce = 0) |> myhandlers
connect!(MyModule.some_observable, model.variable)
I, personally, would opt for your proposal, because you have all callbacks in one place.
Just submitted a PR that would allow for the following usage
@page("/", ui, model = model)
There are two main issues here:
- the original issue of using
Observable.connect!
to propagate updates from a variable in a model to an observable in some module. I feel like this has been resolved, and indeed the best way is@onchange variable MyModule.some_observable[] = variable
. - an offshoot issue (that might warrant opening a new issue for) about using Genie mainly as GUI for a single user. I'm not exactly sure how to adapt my code as it is right now to what you suggested with the global model. I've adopted a stringent MVC framework (following your guide), and it's not clear to me how I can have a global model while at the same time maintain an MVC file and module structure. Maybe that's what you PR does...?
Let me know if you want me to open a new issue for the single-user mode.
This has some similarity to my CameraWidget.jl Maybe you find some inspiration there how I managed the on/off problem.
Wow, I wish that worked with the new GenieFrameworks
(right now just get ERROR: LoadError: UndefVarError:
@vars not defined
). Very cool, thanks.
This has some similarity to my CameraWidget.jl Maybe you find some inspiration there how I managed the on/off problem.
Wow, I wish that worked with the new
GenieFrameworks
(right now just getERROR: LoadError: UndefVarError:
@varsnot defined
). Very cool, thanks.
That was developed with an older version of Stipple. Replace @vars
with @app
. Here are more details on how to convert an older codebase: #176 (comment)
Can this issue be closed?
Can this issue be closed?
Absolutely. However, it would be cool to entertain the Genie's use-case as a single-user GUI. I'll leave it at that for now, but maybe at some point in the future there can be some documentation directed at that solution.