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

Add modal dialogs #1157

Merged
merged 5 commits into from Apr 27, 2016

Conversation

Projects
None yet
2 participants
@wch
Collaborator

wch commented Apr 5, 2016

This adds modal dialogs to Shiny.

Below are three examples (these are in the documentation). We might want to have a simplified API for handling cases like the password/username, where some event has to happen once (successfully) to make the modal go away, and sets a reactive value.

Another thing to consider: the dismissable argument isn't a great name for what it does, so I'm open to other ideas. EDIT: I've changed it to easyClose and updated the examples below.

# Display an important message that can be dismissed only by clicking the
# dismiss button.
shinyApp(
  ui = basicPage(
    actionButton("show", "Show modal dialog")
  ),
  server = function(input, output) {
    observeEvent(input$show, {
      showModal(modalDialog(
        title = "Important message",
        "This is an important message!"
      ))
    })
  }
)

image

# Display a message that can be dismissed by clicking outside the modal dialog,
# or by pressing Esc.
shinyApp(
  ui = basicPage(
    actionButton("show", "Show modal dialog")
  ),
  server = function(input, output) {
    observeEvent(input$show, {
      showModal(modalDialog(
        title = "Somewhat important message",
        "This is a somewhat important message.",
        easyClose = TRUE,
        footer = NULL
      ))
    })
  }
)

image

# Display a modal that requires valid username and password input.
shinyApp(
  ui = basicPage(
    actionButton("show", "Show modal dialog"),
    verbatimTextOutput("loginInfo")
  ),
  server = function(input, output) {
    # A string with the current login status. This is in a reactiveValues
    # object so that it can trigger reactivity.
    vals <- reactiveValues(loginStatus = "Not logged in.")

    # Attempt logging in with a username and password, returning TRUE if
    # successful and FALSE if not.
    login <- function(username, password) {
      # In a real-world use case, this would check against some sort of user
      # database instead of just checking that the values are identical to
      # hard-coded values.
      if (identical(username, "user1") && identical(password, "pass1")) {
        vals$loginStatus <- paste0('Logged in as "', username, '"')
        return(TRUE)

      } else {
        vals$loginStatus <- "Not logged in."
        return(FALSE)
      }
    }

    # Return the UI for a modal dialog with username/password inputs.
    # If 'failed' is TRUE, then display a message that the previous username
    # and password were invalid.
    loginModal <- function(failed = FALSE) {
      modalDialog(
        textInput("username", "Username"),
        passwordInput("password", "Password"),
        span('(Try logging in with "user1" and "pass1")'),
        if (failed)
          div(tags$b("Invalid username/password", style = "color: red;")),

        footer = tagList(
          modalButton("Cancel"),
          actionButton("login", "Log in")
        )
      )
    }

    observeEvent(input$show, {
      showModal(loginModal())
    })

    # When login button is pressed, attempt to log in. If successful, remove the
    # modal. If not show another modal, but this time with a failure message.
    observeEvent(input$login, {
      if (login(input$username, input$password)) {
        removeModal()
      } else {
        showModal(loginModal(failed = TRUE))
      }
    })

    # Display current login status
    output$loginInfo <- renderText({
      vals$loginStatus
    })
  }
)

image

@nuno-agostinho

This comment has been minimized.

Contributor

nuno-agostinho commented Apr 19, 2016

You could add the option of having a small or a large modal dialog through an optional argument size. If size is large then class = "modal-dialog modal-lg", else if small then class = "modal-dialog modal-lg", else just let it be class = "modal-dialog".

Small modal windows are nice for alerts! :)

And regarding alerts, it'd also be nice if we could change the ID of modal-header to allow to style modals like this using CSS: http://bootsnipp.com/snippets/featured/colored-modal-headings

wch added some commits Apr 21, 2016

@wch wch merged commit 7e303b4 into master Apr 27, 2016

0 of 2 checks passed

continuous-integration/travis-ci/pr The Travis CI build is in progress
Details
continuous-integration/travis-ci/push The Travis CI build is in progress
Details

@wch wch deleted the modal branch Apr 27, 2016

@wch

This comment has been minimized.

Collaborator

wch commented Apr 27, 2016

@nuno-agostinho You can select the modal-header with a selector like #shiny-modal .modal-header. Also, although sizes could be useful, I think in this case the costs (complexity, maintenance) outweigh the benefits. There's also the new notification system if you want to show smaller pieces of information.

div(id = "shiny-modal", class = "modal fade", tabindex = "-1",
`data-backdrop` = if (!easyClose) "static",
`data-keyboard` = if (!easyClose) "false",

This comment has been minimized.

@nuno-agostinho

nuno-agostinho Apr 28, 2016

Contributor

@wch I don't think it adds that much complexity since it's just allowing a new argument size in the modalDialog function (as NULL by default) and adding:

size <- switch(size, large=" modal-lg", small=" modal-sm", NULL)
div (class = paste0("modal-dialog", size), # rest of the code

Anyway, I can't wait to try the new notification system. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment