vue-r / vueR

vue.js for R
https://vue-r.github.io/vueR
Other
140 stars 14 forks source link

Enhance the ability of shiny and vue to communicate with each other #14

Open kaipingyang opened 2 months ago

kaipingyang commented 2 months ago

As a shiny enthusiast, I am learning vue.

Thanks to vueR for letting us see the possibility of developing new shiny components based on shiny and vueR!

To make it easier to develop shiny components based on vueR, I think it is necessary to create new functions to enhance the ability of shiny and vue to communicate with each other

Ramnathv's vuer package https://github.com/ramnathv/vuer, inspired by the vueR, provides a set of functions to enhance shiny and vue's ability to communicate with each other:

Vue()
vueProxy()
vueUpdateData()

Unfortunately, this package has not been updated for five years.

Is it possible to incorporate his ideas into vueR?

timelyportfolio commented 1 month ago

@kaipingyang thanks so much for posting the issue and very happy/willing to consider new functionality. I had forgotten that Ramnath had done those experiments/extensions. Are there are any particular use cases or examples that you have in mind? I think #6, #7 (could swap out pinia for the deprecated vuex), #12 might be related.

kaipingyang commented 1 month ago

Thanks for your reply. I will learn https://github.com/vue-r/vueR/issues/6, https://github.com/vue-r/vueR/issues/7, https://github.com/vue-r/vueR/issues/12.

Here are a few use cases from the vuer package website, which still works with R4.4:

Vue -> Shiny

This example binds the Vue instance's data property name to shiny's input$name, making it easy to use Vue instance data in the server:

library(shiny)
library(vuer)
ui <- fluidPage(theme = shinythemes::shinytheme("cosmo"),
  tags$div(
    tags$label('Enter your name'),
    tags$input(type = "text", "v-model" = "name"),
    uiOutput("greeting")
  ) %>% 
  Vue(
    data = list(name_ = "")
  )
)

server <- function(input, output, session){
  output$greeting <- renderUI({
    tags$p(paste("Hello", input$name))
  })
}

shinyApp(ui = ui, server = server)

image

Shiny -> Vue

This example uses vueProxy and vueUpdateData in the server to pass the result of the input$plot_brush to the Vue data in the frontend, making it easy to change the Vue instance data in the server:

library(shiny)
library(ggplot2)
library(vuer)
ui <- fluidPage(theme = shinythemes::shinytheme("cosmo"),
  titlePanel(title = 'Shiny -> Vue'),
  mainPanel(
    plotOutput('plot', brush = brushOpts('plot_brush'), height = '300'),
    tags$pre("v-if" = "plot_brush !== null", 
      tags$code("{{plot_brush}}")
    ) %>% 
      Vue(data = list(plot_brush = c()), elementId = "app")
  )
)

server <- function(input, output, session){
  output$plot <- renderPlot({
    ggplot(mtcars, aes(x = mpg, y = wt)) +
      geom_point()
  })
  observeEvent(input$plot_brush, {
    vueProxy("app") %>% 
      vueUpdateData(plot_brush = input$plot_brush$coords_img)
  })
}
shinyApp(ui = ui, server = server)

image

timelyportfolio commented 1 month ago

@kaipingyang hi, thanks for your patience. I have spent some time thinking through this and will relay my thoughts below. Also, I found a bug in vue3 that should be corrected in the dev branch, so please remotes::install_github("vue-r/vueR@dev") before trying.

Vue -> Shiny

I do not like the magical _ at the end of the variable especially with vue3. Watchers in vue3 can take many different forms and offer lots of options that are not available/workable with the magical _ approach. For instance, deep watchers and dot-delimited-paths are two that I use frequently. I prefer to leave watch to the user for full control. Nevertheless, I do agree that this package is sorely lacking documentation and examples that enable a user to watch effectively. I will try to resolve this over the weekend, but appreciate any help. Also, maybe we could write a helper function to reduce some of the boilerplate burden. For now, here is how we would replicate Ramnath's example with vue3.

library(shiny)
library(vueR)

ui <- fluidPage(
  theme = shinythemes::shinytheme("cosmo"),
  tags$div(
    id = "app",
    tags$label('Enter your name'),
    tags$input(type = "text", "v-model" = "name"),
    uiOutput("greeting"),
    tags$button('@click'="count++", "{{ count }}"),
    vue3(
      app = list(
        el = "#app",
        data = list(name = "", count = 0),
        watch = list(
          name = htmlwidgets::JS("function(newName, oldName) {Shiny.setInputValue('name', newName)}")
        )
      )
    )
  )
)

server <- function(input, output, session){
  output$greeting <- renderUI({
    tags$p(paste("Hello", input$name))
  })
}

shinyApp(ui = ui, server = server)

vueProxy / vueUpdateData

I am a big fan of the proxy mechanism and agree this would be a useful addition to the package. Dealing with nested data updates will be tricky though, and I probably will focus on the shallow update case.

timelyportfolio commented 1 month ago

@kaipingyang also you might be interested in https://www.youtube.com/watch?v=SkFLHbHrPD4&t=2s and https://github.com/timelyportfolio/vite-vue-r/.