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

Allow to connect! app variable to an observable #218

Closed
yakir12 opened this issue Aug 28, 2023 · 13 comments
Closed

Allow to connect! app variable to an observable #218

yakir12 opened this issue Aug 28, 2023 · 13 comments
Assignees

Comments

@yakir12
Copy link
Contributor

yakir12 commented Aug 28, 2023

[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
@PGimenez
Copy link
Member

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

@PGimenez PGimenez self-assigned this Sep 6, 2023
@hhaensel
Copy link
Member

hhaensel commented Sep 7, 2023

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.

@yakir12
Copy link
Contributor Author

yakir12 commented Sep 7, 2023

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

@hhaensel
Copy link
Member

hhaensel commented Sep 7, 2023

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)

@hhaensel
Copy link
Member

hhaensel commented Sep 7, 2023

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

@hhaensel
Copy link
Member

hhaensel commented Sep 7, 2023

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.

@hhaensel
Copy link
Member

hhaensel commented Sep 7, 2023

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.

@hhaensel
Copy link
Member

hhaensel commented Sep 8, 2023

Just submitted a PR that would allow for the following usage

@page("/", ui, model = model)

@yakir12
Copy link
Contributor Author

yakir12 commented Sep 12, 2023

There are two main issues here:

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

@yakir12
Copy link
Contributor Author

yakir12 commented Sep 12, 2023

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.

@essenciary
Copy link
Member

essenciary commented Sep 14, 2023

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.

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)

@essenciary
Copy link
Member

Can this issue be closed?

@yakir12
Copy link
Contributor Author

yakir12 commented Sep 14, 2023

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.

@yakir12 yakir12 closed this as completed Sep 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants