r-world-devs / shinyGizmo

https://r-world-devs.github.io/shinyGizmo
Other
19 stars 0 forks source link

How to use mergeCalls()? #21

Closed stla closed 1 year ago

stla commented 2 years ago

Hello,

I'm trying to use mergeCalls for one hour now. This does not work (throws an error):

ui <- fluidPage(
  navbarPage(
    "",
    tabPanel(
      "tab",
      conditionalJS(
        div(
          style = "display: none", # to prevent the flash at start-up
          id = "Sidebar",
          sidebarPanel(
            sliderInput(
              "obs", "Number of observations:",
              min = 0, max = 1000, value = 500
            )
          )
        ),
        condition = "input.toggleSidebar % 2 === 1",
        jsCall = mergeCalls(
          jsCalls$animateVisibility("fadeInLeft", "fadeOutLeft", duration = 1000),
          jsCalls$animateVisibility("swing", "shakeX", duration = 1000)
        )
      ),

      mainPanel(
        actionButton(
          "toggleSidebar", "Toggle sidebar", 
          class = "btn-primary"
        )
      )
    )
  )
)

server <-function(input, output, session) {

}

It seems to me that the class animate_call is missing. With the following code, there's no error, but the second animation does not show up:

        jsCall = lapply(mergeCalls(
          jsCalls$animateVisibility("fadeInLeft", "fadeOutLeft", duration = 1000),
          jsCalls$animateVisibility("swing", "shakeX", duration = 1000)
        ), function(x){
          class(x) <- c(class(x), "animate_call")
          x
        })
stla commented 2 years ago

I think that concatenating two animations doesn't work, because the second one will not wait for the first one to finish. One has to put the second one in the callback of the first one.

stla commented 2 years ago

Here is a working way:

#' Define an animation
#'
#' Creates an `animation` object for usage in \link{runAnimation}.
#'
#' @param effect Animation effect used name to be applied.
#'     Check \link{.cssEffects} object for possible options.
#' @param delay Delay of animation start (in milliseconds).
#' @param duration Duration of animation (in milliseconds).
#'
#' @return A named list with class `animation`.
#' @export
animation <- function(effect, delay = 0, duration = 1000) {
  out <- list(
    "effect"   = match.arg(effect, .cssEffects),
    "delay"    = delay,
    "duration" = duration
  )
  class(out) <- "animation"
  out
}

animationRule <- function(anim, callbackBody){
  settings <- json_settings(
    delay    = anim$delay,
    duration = anim$duration,
    callback = I(glue::glue("function() {{{callbackBody}}}"))
  )
  htmlwidgets::JS(glue::glue(
    "$(this).animateCSS('{anim$effect}', {settings});"
  ))
}

chainTwoAnimations <- function(anim, rule){
  animationRule(anim, callbackBody = rule)
}

chainAnimations <- function(...){
  anims <- list(...)
  nanims <- length(anims)
  lastanim <- anims[[nanims]]
  init <- animationRule(lastanim, callbackBody = "")
  if(nanims == 1L){
    return(init)
  }
  Reduce(chainTwoAnimations, anims[-nanims], init, right = TRUE)
}

#' Helpful methods for custom callback setup
#'
#' Can be used as a `true` or `false` argument for custom method of \link{js_calls}.
#'
#' @name custom-callbacks
#' @param ... Animation object(s) created with \link{animation}; if multiple
#'   animation objects are given then the animations will be chained.
#' @param ignoreInit Should the animation be skipped when application is in initial state?
#'
#' @examples
#' conditionalJS(
#'   shiny::tags$button("Hello"),
#'   "input.value > 0",
#'   jsCalls$custom(true = runAnimation("tada"))
#' )
#'
#' @export
runAnimation <- function(..., ignoreInit = TRUE) {
  check <- TRUE # TODO
  ignore_init <- if (ignoreInit) "true" else "false"
  chain <- chainAnimations(...)
  rule <- htmlwidgets::JS(glue::glue(
    "var $element = $(this);",
    "if (!{ignore_init} || $element.data('data-call-initialized')) {{",
      "{chain};",
    "}}"
  ))
  class(rule) <- c(class(rule), "animate_call")
  return(rule)
}

Usage example:

ui <- fluidPage(
  actionButton("value", "Click me"),
  br(), br(),
  conditionalJS(
    tags$h1("Hello", style = "display: none;"),
    "input.value > 0",
    jsCalls$custom(
      true = runAnimation(animation("tada"), animation("swing")),
      false = JS("$(this).hide();")
    )
  )
)
stla commented 2 years ago

A couple of suggestions:

krystian8207 commented 2 years ago

@stla thank you for filing this issue. I've fixes inheritance of animate_call class in this branch. The issue with running multiple animations seems to be not supported in chain (what I think we're doing here by merging in a way) but should be done with using animateCSS callback: https://github.com/craigmdennis/animateCSS#chain-multiple-animations

Regarding your last suggestion for solution I'll take a look at it soon and respond with my thought. But for now, thanks a lot for your help and contribution here :)

stla commented 2 years ago

but should be done with using animateCSS callback

This is exactly what my code does.

Cheers.

krystian8207 commented 2 years ago

@stla Ahh yes, I can see it now. I do really like your suggestion, could you please create a PR (or should I do it)? Thanks!

krystian8207 commented 1 year ago

Closing as this was fixed in v0.3 (already on CRAN).