timelyportfolio / sweetalertR

R htmlwidget for sweetalert by @t4t5
MIT License
21 stars 2 forks source link

Using sweetalertR inside Shiny apps? #1

Open rpodcast opened 9 years ago

rpodcast commented 9 years ago

Hello, I just came across your blog entry on sweetalertR and it looks great! I'd like to incorporate this functionality in some of my Shiny applications I'm developing at work. What would be the best way to use this in a Shiny app? Ideally it would be triggered after the user hits an action button much like what you did in your R markdown example. This example from the Shiny gallery uses a javascript file called message-handler.js to call the basic alert function, but I'm wondering if there's an easy way to swap that with the functions offered in this package? I know @daattali has created the excellent shinyjs package which contains a function called info to display normal alerts, so I guess another approach might be to use its extendShinyjs paradigm but I'm pretty new to using customized javascript solutions in Shiny Apps. Sorry for the long text!

daattali commented 9 years ago

Hey, I wasn't aware of this library, it does look awesome! So much nicer than real alerts indeed. Really good to know.

I'm a bit embarrassed to admit that I haven't actually looked into htmlwidgets yet myself so I'm very unfamiliar with how to run these things... but after playing around with it for a bit it seems to me that something like this will work in a shiny app:

library(shiny)
library(sweetalertR)
runApp(shinyApp(
  ui = fluidPage(
    actionButton("go", "Go"),
    sweetalert(selector = "#go", text = "hello", title = "world")
  ),

  server = function(input, output, session) {
  }
))

Which works really nicely :)

Although since I was summoned here, I'll say that I don't love the idea of defining the alert box in the input -- to me, that should be part of the logic rather than part of the UI. It's very possible that my previous code block is not the correct way of using this in a shiny app though. Is it possible to define the alert in the server?

Anyway, just as a fun exercise, I wanted to see if this could be incorporated into shinyjs and it does seem like it, but I won't do it unless there's demand for it. Here's a quick simple example of how to use shinyjs as an interface to sweetalert

library(shiny)

jscode <- "shinyjs.swal = function(params) { swal.apply(this, params); }"

runApp(shinyApp(
  ui = fluidPage(
    tags$head(
      includeScript("https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js"),
      includeCSS("https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css")
    ),
    shinyjs::useShinyjs(),
    shinyjs::extendShinyjs(text = jscode),

    textInput("title", "Title"),
    textInput("text", "Text"),
    selectInput("type", "Type", c("warning", "error", "success")),
    actionButton("swal", "Action!")
  ),

  server = function(input, output, session) {
    observeEvent(input$swal, {
      shinyjs::js$swal(input$title, input$text, input$type)
    })
  }
))

I hope my presence here is ok @timelyportfolio

timelyportfolio commented 9 years ago

@daattali I have been watching shinyjs and your other projects closely, so I am delighted that you are here. You bring up a couple of very good points. I'll start by saying that I view all of my htmlwidgets as experimental, and really the whole concept of htmlwidgets as experimental, so discussions like these are really valuable.

In my mind, htmlwidgets are designed so that a R user can leverage and deploy JS/CSS/HTML straight from R with basically no knowledge of JS/CSS/HTML. I think shinyjs is the opposite in that it is designed to make life easier for users who know both R and JS/CSS/HTML.

sweetalertR is tricky since it almost always will require a trigger before it springs into action. Most other htmlwidgets are expected to just appear. I struggled with the design of sweetalertR. I did not want to assume that the trigger for sweetalertR would always be a button, so I wanted the R user to be able to provide any selector, but this of course quickly starts to assume that the user knows some HTML. In this case, I felt like the user would probably want full customization of the tag. In katexR I approached this a little differently with a custom toHTML function. This makes both the tag and the katexR one function where sweetalertR requires two functions -- a tag and the sweetalert.

One other benefit to htmlwidgets (that I'm not sure is discussed enough) is dependency and conflict management. Adding CSS and JS can be a pain and conflicts can quickly arise. In a lot of my widgets, I'll allow an option just so the dependencies are injected, so in sweetalertR, you can see in lines. In this case htmlwidgets and shinyjs might play very well together. As an example, let's modify your code just a bit. In this simple case, the benefit is less obvious, but with multiple CSS/JS dependencies, you can quickly remove a lot of code.

library(shiny)
library(sweetalertR)

jscode <- "shinyjs.swal = function(params) { swal.apply(this, params); }"

runApp(shinyApp(
  ui = fluidPage(
    shinyjs::useShinyjs(),
    shinyjs::extendShinyjs(text = jscode),

    textInput("title", "Title"),
    textInput("text", "Text"),
    selectInput("type", "Type", c("warning", "error", "success")),
    actionButton("swal", "Action!"),
    sweetalert()
  ),

  server = function(input, output, session) {
    observeEvent(input$swal, {
      shinyjs::js$swal(input$title, input$text, input$type)
    })
  }

This doesn't really apply to sweetalertR, but I also should mention htmlwidgets currently are only expected to be outputs and not inputs (see https://github.com/ramnathv/htmlwidgets/issues/19). However, you'll see in some htmlwidgets code like this in leaflet and parcoords that allows htmlwidgets to communicate back to R/Shiny.

I'll stop for now, but happy to continue the discussion.

timelyportfolio commented 9 years ago

@thercast did the discussion above help?

Off topic, but I enjoyed the couple of episodes of R podcast. Sounds like a new R podcast has started.

rpodcast commented 9 years ago

Thank you @timelyportfolio and @daattali! I ran the updated example on my personal server and it works great. Unfortunately the Shiny servers I use at work don't have the necessary libraries required by the v8 package (hence I can't use shinyjs::extendShinyjs()), but I'll work with our IT group to get them installed.

Thanks for your kind feedback on the previous episodes! I'm getting much closer to re-launching the R-Podcast and I'd like to have a lot more interviews than in the previous run. I plan on doing a series of episodes dedicated to Shiny and htmlwidgets, and it would be excellent to have both of you as future guests on the show. If you're interested please send a message to my podcast email account (on my GitHub profile).

daattali commented 9 years ago

@timelyportfolio you're right, the dependency management is actually a really nice feature. I'd just like to raise another reason whyI like being able to call JS functions from the server and not have them completely coupled to a click on a specific tag. In my past life as a webdev I often ran into situations where I want to trigger an action (such as an alert box) based on some logic that is NOT a button press. For example, if the user presses a "Submit" button and there's an error, I'd like to show an error alert box. Or if the user presses on a button but another input field is not valid, you might want to trigger the alert. I'm not sure if you agree that it's a useful use of alert boxes -- maybe for 99% of shiny users having an alert box on click is all they need.

@thercast it looks like you started these podcasts before I even knew what R is, long time! I did see your last one with Yihui a few months ago, it's a good idea, I would very much support having more episodes.

Gnolam commented 8 years ago

Thank you both (timelyportfolio and daattali) for publishing workable examples for Shiny. I am a huge fan of shinyjs (daattali you rock!) and happy to see that it can help as well.

SWAL is something I was looking for quite a long time! Happy to use and advertise it!!! Thank you all once again

Gnolam commented 8 years ago

@daattali is there any way to pass named arguments to function declared through shinyjs::extendShinyjs()? E.g. it would be great to shinyjs::js$swal(title = input$title) but it reports "SweetAlert: SweetAlert expects at least 1 attribute!"

daattali commented 8 years ago

Yes, you can pass named arguments. What you cannot do with shinyjs is use both named and unnamed arguments in the same function call. See this section of the README for more info.

The error you were seeing was an error on sweetalert's part, not coming from shinyjs, but it's happening because in my previous code I called sweetalert using swal.apply(this, params), and params assumed that it was an array with those 3 elements. Anyway, here's a basic example to show you how to that you can call swal with named argument instead

library(shiny)

jscode <- "
shinyjs.swal = function(params) { 
  var defaultParams = {
    title : null,
    text : null,
    type : null  
  };
  params = shinyjs.getParams(params, defaultParams);
  swal(params);
}"

runApp(shinyApp(
  ui = fluidPage(
    tags$head(
      includeScript("https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js"),
      includeCSS("https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css")
    ),
    shinyjs::useShinyjs(),
    shinyjs::extendShinyjs(text = jscode),

    textInput("title", "Title"),
    textInput("text", "Text"),
    selectInput("type", "Type", c("warning", "error", "success")),
    actionButton("submit", "Action!")
  ),

  server = function(input, output, session) {
    observeEvent(input$submit, {
      shinyjs::js$swal(title = input$title, text = input$text, type = input$type)
    })
  }
))
Gnolam commented 8 years ago

Thank you daattali, you are a star! It works. And you are right I have missed this passage in your Read.Me Thank you once again!

csese commented 8 years ago

Hello, Thank you very much @daattali & @timelyportfolio for for shinyjs and sweetalertR. I am trying to use swal in a shinydashboard. I would like to have an alert allowing me to change tabs. Unfortunately, I am not able to make it work. Here is what I was able to come up with. I would like to have the confirm button to switch tab.

library(shiny)

jscode <- "
shinyjs.swal = function(params) { 
var defaultParams = {
title : null,
text : null,
type : null  
};
params = shinyjs.getParams(params, defaultParams);
swal(params);
}"

ui <-  shinydashboard::dashboardPage(
  shinydashboard::dashboardHeader(title = "AXA Geocoding"),
  shinydashboard::dashboardSidebar(
    tags$head(
      tags$script(src = "resetInputFile.js"),
      tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js"),
      tags$link(rel = "stylesheet", type = "text/css", href = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css")
    ),
    shinydashboard::menuItem("Tab 1", tabName = 'tab1'),
    shinydashboard::menuItem("Tab 2", tabName = 'tab2')
  ),

  shinydashboard::dashboardBody(
    shinyjs::useShinyjs(),
    shinyjs::extendShinyjs(text = jscode),
    shinydashboard::tabItems(
      shinydashboard::tabItem(tabName = 'tab1',
                              actionButton("submit1", "Action Tab1!")
                              # function(){
                              # "$($('#nav a')[1]).tab('show');"
                              # })
      ),      
      shinydashboard::tabItem(tabName = 'tab2',
                              actionButton("submit2", "Action Tab2")

      ))
  )
)

server <-  function(input, output, session) {
  observeEvent(input$submit1, {
    shinyjs::js$swal(title = 'Computation completed',
                     type = 'success', showCancelButton = T,
                     confirmButtonText = 'Go to result page.',
                     cancelButtonText = 'Stay on computation tab.')
  })
}
shinyApp(ui = ui, server = server)
daattali commented 8 years ago

It doesn't look like you have any code here that defines what to do when the confirm button vs cancel button is clicked. I wrote the code above after looking at the very basic SWAL documentation and just wrote it quickly to make it show a popup. If you want to have functionality happen when you click the buttons, you'd have to read their documentation to see how to do that.

csese commented 8 years ago

@daattali. Thank you for your answer. My mistake, the following code is showing how I would do that using javascript:

library(shiny)

jscode <- "
shinyjs.swal = function(params) { 
var defaultParams = {
title : null,
text : null,
type : null  
};
params = shinyjs.getParams(params, defaultParams);
swal(params);
}"

ui <-  shinydashboard::dashboardPage(
  shinydashboard::dashboardHeader(title = "AXA Geocoding"),
  shinydashboard::dashboardSidebar(
    tags$head(
      tags$script(src = "resetInputFile.js"),
      tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js"),
      tags$link(rel = "stylesheet", type = "text/css", href = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css")
    ),
    shinydashboard::menuItem("Tab 1", tabName = 'tab1'),
    shinydashboard::menuItem("Tab 2", tabName = 'tab2')
  ),
  shinydashboard::dashboardBody(
    shinyjs::useShinyjs(),
    shinyjs::extendShinyjs(text = jscode),
    shinydashboard::tabItems(
      shinydashboard::tabItem(tabName = 'tab1',
                              actionButton("submit1", "Action Tab1!")
      ),      
      shinydashboard::tabItem(tabName = 'tab2',
                              actionButton("submit2", "Action Tab2")
      ))
  ))
server <-  function(input, output, session) {
  observeEvent(input$submit1, {
    shinyjs::js$swal(title = 'Computation completed',
                     type = 'success', showCancelButton = T,
                     confirmButtonText = 'Go to result page.',
                     cancelButtonText = 'Stay on computation tab.', function(){
                       "$($('#nav a')[1]).tab('show');"
                     })
  })
}
shinyApp(ui = ui, server = server)

it returns the following error:

Error in : shinyjs: you cannot mix named and unnamed arguments in the same function call (function: shinyjs::js$swal)
daattali commented 8 years ago

@csese It doesn't look like you fully understand either how my sample code above works or how extendShinyjs() works. By calling js$swal(), the shinyjs.swal() function is called. If you look at the JS code, that function expects 3 parameters: title, text, type. You're trying to pass a bunch of other parameters and it doesn't know what to do with them. I think those other parameters in your R code are parameters you intend on having the javascript call, not R.

I think this is closer to the code you want (notice that after clicking the confirm button I'm just showing an alert message because your javascript code to switch tabs doesn't seem to work, but that's a purely javascript question)

jscode <- "
shinyjs.swal = function(params) { 
var defaultParams = {
title : null,
text : null,
type : null  
};
params = shinyjs.getParams(params, defaultParams);
swal({title : params.title, text : params.text, type : params.type,
     showCancelButton : true, confirmButtonText : 'Go to result page.',
     cancelButtonText : 'Stay on computation tab.'},
     function(){ alert('switch'); });
}"

ui <-  shinydashboard::dashboardPage(
  shinydashboard::dashboardHeader(title = "AXA Geocoding"),
  shinydashboard::dashboardSidebar(
    tags$head(
      #tags$script(src = "resetInputFile.js"),
      tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js"),
      tags$link(rel = "stylesheet", type = "text/css", href = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css")
    ),
    shinydashboard::menuItem("Tab 1", tabName = 'tab1'),
    shinydashboard::menuItem("Tab 2", tabName = 'tab2')
  ),
  shinydashboard::dashboardBody(
    shinyjs::useShinyjs(),
    shinyjs::extendShinyjs(text = jscode),
    shinydashboard::tabItems(
      shinydashboard::tabItem(tabName = 'tab1',
                              actionButton("submit1", "Action Tab1!")
      ),      
      shinydashboard::tabItem(tabName = 'tab2',
                              actionButton("submit2", "Action Tab2")
      ))
  ))
server <-  function(input, output, session) {
  observeEvent(input$submit1, {
    shinyjs::js$swal(title = 'Computation completed',
                     type = 'success')
  })
}
shinyApp(ui = ui, server = server)

Also, the file resetInputFile.js seems to be unrelated but because it's in the code you include it keeps resulting in errors for us.

csese commented 8 years ago

@daattali Thank you for your explanations. Here is a working code to switch tab using sweetalert.

jscode <- "
shinyjs.swal = function(params) { 
var defaultParams = {
title : null,
text : null,
type : null  
};
params = shinyjs.getParams(params, defaultParams);
swal({title : params.title, text : params.text, type : params.type,
showCancelButton : true, confirmButtonText : 'Go to result page.',
cancelButtonText : 'Stay on computation tab.'},
function(){  $tablinks = $('ul.sidebar-menu').find('a').filter('[data-value=tab2]');
                    $tablinks.tab('show'); 
});
}"

header <-   shinydashboard::dashboardHeader(title = "AXA Geocoding")
sidebar <- shinydashboard::dashboardSidebar(
                                            tags$head(
                                              tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js"),
                                              tags$link(rel = "stylesheet", type = "text/css", 
                                                        href = "https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css")
                                            ),
                                            sidebarMenu(id = 'sidebarmenu',
                                            shinydashboard::menuItem("Tab 1", tabName = 'tab1'),
                                            shinydashboard::menuItem("Tab 2", tabName = 'tab2'))
)

body <-   shinydashboard::dashboardBody(
  shinyjs::useShinyjs(),
  shinyjs::extendShinyjs(text = jscode),
  shinydashboard::tabItems(
    shinydashboard::tabItem(tabName = 'tab1',
                            actionButton("submit1", "Action Tab1!")),      
    shinydashboard::tabItem(tabName = 'tab2',
                            actionButton("submit2", "Action Tab2")))
)

ui <- shinydashboard::dashboardPage(
  header = header,
  sidebar = sidebar,
  body = body
)

server <-  function(input, output, session) {
  observeEvent(input$submit1, {
    shinyjs::js$swal(title = 'Computation completed',
                     type = 'success')
  })
}
shinyApp(ui = ui, server = server)
ghost commented 5 years ago

Is there a way to close the shinyjs::js$swal messages with a line of code after finishing a long calculation? trying to figure it out but no luck so far:

see also: SO question


jscodeswal <- c(
    "shinyjs.swalGIF= function(params) { 
        var defaultParams = {title: null, html : null, imageUrl: null };
        params = shinyjs.getParams(params, defaultParams);
        swal({title : params.title, html : params.html,  imageUrl : params.imageUrl,
            animation : false,
            showConfirmButton : false,
            showCancelButton : false,
            allowOutsideClick: false,
            allowEscapeKey: false
        });
    };"
)
if (interactive()) {
    require(shiny)
    require(shinyalert)
    require(shinyjs)
    shinyApp(
        ui = fluidPage(
            shinyjs::useShinyjs(),
            shinyjs::extendShinyjs(text = jscodeswal),
            tags$head(
                includeScript("https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.js"),
                includeCSS("https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.0.1/sweetalert.min.css")
            ),
            actionButton("btn", "Click me")
        ),
        server = function(input, output, session) {
            observeEvent(input$btn, {
                # Show a simple modal
                shinyjs::js$swalGIF(title = '<span style="color:#339FFF; font-size: 30px">Test message<span>',
                                    html = paste('<br><span style = "font-weight: normal; color:#797979">Please close me when done!<br><br>'),
                                    imageUrl = 'https://media.giphy.com/media/4HgDESJLG1JSmqTOnH/giphy.gif')

                # close me after finish long calculation

                    })
        }
    )
}