Enhance the ability of shiny and vue to communicate with each other
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?
@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.
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)
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)
@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.
@kaipingyang also you might be interested in https://www.youtube.com/watch?v=SkFLHbHrPD4&t=2s and https://github.com/timelyportfolio/vite-vue-r/.
Help Request: shiny.element Architecture Choice
@timelyportfolio Thanks for your suggestion, I tried to create an R user-friendly lightweight R package shiny.element after studying your experiment content.
I'm encountering an architecture issue while developing the shiny.element package (wrapping Element-UI based on vueR). link to Repo kaipingyang/shiny.element
Current Architecture
Each interactive component creates an independent Vue instance. shiny.element: R/el_button.R:41-56
Layout components (el_row, el_col) are pure HTML tag generators. shiny.element: R/el_layout.R
el_container creates a Vue instance but uses template = as.character() to render child components. shiny.element: R/el_container.R
Problem
All layout components cannot nest interactive components:
el_row/el_col: When nestingel_button, displays{{label}}instead of button textel_container: When usingas.character()to convert child components to template strings, the nestedel_button's Vue instance is destroyed, resulting in a blank page- The original
el_containercan only nest pure layout components (el_header,el_main), not interactive components
ui <- el_page(
el_container(
el_header("Header"),
el_main("Main")
)
)
server <- function(input, output, session) {}
shinyApp(ui, server)
ui <- el_page(
el_container(
el_row(
el_col(span = 12, el_button("Button", type = "primary"))
)
)
)
server <- function(input, output, session) {}
shinyApp(ui, server)
# Result: displays blank page
Two Approaches
Approach 1: Keep independent Vue instances
- ✅ Simple code,
update_*()works normally - ✅ Shiny/bslib layout components can nest interactive element components
- ❌ Layout components cannot nest interactive element components
Approach 2: Global Vue instance
- ✅ Can nest any components
- ❗ Requires refactoring all components, complex architecture
- ❗ Requires the user to mount the vue instance manually
Questions
- Does vueR recommend independent or global Vue instances?
- How to handle Vue instance nesting?
- Is there a compromise solution?
Thank you for your guidance!