RinteRface / shinydashboardPlus

extensions for shinydashboard
https://shinydashboardplus.rinterface.com
Other
454 stars 77 forks source link

flipBox does not flip when rendered for the second time using renderUI #89

Closed stelmath closed 3 years ago

stelmath commented 3 years ago

Hello and thanks for the great package. I have seen other issue posts referring to the flip functionality of flipBox under the server renderUI function.

I have modified the manual example a bit to showcase the scenario that I am having:

library(shiny)
library(shinydashboard)
library(shinydashboardPlus)

shinyApp(
  ui = dashboardPage(
    dashboardHeader(),
    dashboardSidebar(),
    dashboardBody(
      setShadow("card"),
      fluidRow(
        column(
          width = 6,
          align = "center",
          selectInput('names', label= 'Pick a name to display the flipbox',
                      choices = c('mary', 'john', 'jane'), multiple = FALSE),
          uiOutput('flip_box')
          )
        )

      )

  ),
  server = function(input, output) {
    output$flip_box <- renderUI({

      req(input$names)

      # I need to use input values from various input widgets as parameter
      # values in flipBox, that is why I am using the renderUI
      # but the 'back side' button does not work on the second rendering
      # of the flipbox, only the first time it renders
      flipBox(
        id = 1,
        main_img = "https://image.flaticon.com/icons/svg/149/149076.svg",
        header_img = "https://image.flaticon.com/icons/svg/119/119595.svg",
        front_title = input$names,  
        back_title = "About John",
        "The first time this flipbox is displayed, the More button works fine.
        But, when you select another name from the dropdown menu, and the flipbox is re-rendered, 
        the More button does not work",
        fluidRow(
          dashboardLabel("Label 1", status = "info"),
          dashboardLabel("Label 2", status = "success"),
          dashboardLabel("Label 3", status = "warning"),
          dashboardLabel("Label 4", status = "primary"),
          dashboardLabel("Label 5", status = "danger")
        ),
        hr(),
        back_content = tagList(
          column(
            width = 12,
            align = "center",
            sliderInput(
              "obs", 
              "Number of observations:",
              min = 0, 
              max = 1000, 
              value = 500
            )
          ),
          plotOutput("distPlot")
        ))
    })

        output$distPlot <- renderPlot({
          hist(rnorm(input$obs))
        })    

  }
)

As you can see, when you select a different name from the selectInput, let's say "John" then the box does not flip anymore. Any help with this? The flipping is a cool feature that I don't want to sacrifice.

stelmath commented 3 years ago

Looks like the event listener is gone after the second rendering of the flipBox. Initial run of the script and before selecting any name from the dropdown: image

After selecting a name from the dropdown, other than the default one: image

stelmath commented 3 years ago

@DivadNojnarg I think I found the cause of this. I will make a PR pretty soon - please be aware. Thanks

DivadNojnarg commented 3 years ago

I had to entirely rework the flipBox since it was causing a lot of issues (unable to devtools::check(), ...). Now eveything happens JS side, in a proper input binding. This allows to have a much more clean code (R side), an updateFlipBox, know the state of the box at any time.

var flipBoxBinding = new Shiny.InputBinding();

  $.extend(flipBoxBinding, {
    find: function(scope) {
      return $(scope).find(".flipbox");
    },
    // Given the DOM element for the input, return the value
    getValue: function(el) {
      return $(el).find('.card-front').hasClass('active');
    },

    setValue: function(el, value) {
      var currentSide = $(el).find('.active');
      if ($(el).data('rotate') === 'hover') {
        if ($(currentSide).hasClass('card-front')) {
          $(currentSide).trigger('mouseenter');
        } else {
          $(currentSide).trigger('mouseleave');
        }
      } else if ($(el).data('rotate') === 'click') {
        $(currentSide).trigger('click'); 
      }
    },

    // see updateAccordion
    receiveMessage: function(el, data) {
      this.setValue(el, data);
    },
    _clickOnFront: function(el) {
      $(el).find('.card-front')
        .css({
          "-webkit-transform": "perspective(1600px) rotateY(-180deg)",
          "transform": "perspective(1600px) rotateY(-180deg)"
        })
        .toggleClass('active');
      $(el).find('.card-back')
        .css({
          "-webkit-transform": "perspective(1600px) rotateY(0deg)",
          "transform": "perspective(1600px) rotateY(0deg)"
        })
        .toggleClass('active');
    },
    _clickOnBack: function(el) {
      $(el).find('.card-front')
        .css({"-webkit-transform": "", "transform": ""})
        .toggleClass('active');
      $(el).find('.card-back')
        .css({"-webkit-transform": "", "transform": ""})
        .toggleClass('active');
    },
    subscribe: function(el, callback) {
      var self = this; // this will not work inside event listeners since it will
      // refer to the element we clicked on and not the input binding object!!!

      // use the data object to identify the trigger
      if ($(el).data('rotate') === 'hover') {
        $(el).find('.card-front').on('mouseenter', function() {
          self._clickOnFront(el);
          callback();
        });

        $(el).find('.card-back').on('mouseleave', function() {
          self._clickOnBack(el);
          callback();
        });

      } else if ($(el).data('rotate') === 'click') {
        // click front
        $(el).on('click', '.card-front', function(e) {
          self._clickOnFront(el);
          callback();
        });

        // click back
        $(el).on('click', '.card-back', function(e) {
          self._clickOnBack(el); 
          callback();
        });
      }
    },

    unsubscribe: function(el) {
      $(el).off(".flipBoxBinding");
    }
  });

  Shiny.inputBindings.register(flipBoxBinding, "flipbox-input");

Example:

remotes::install_github("RinteRface/shinydashboardPlus")

library(shiny)
 library(shinydashboard)
 library(shinydashboardPlus)
 shinyApp(
  ui = dashboardPage(
    dashboardHeader(),
    dashboardSidebar(),
    dashboardBody(
      fluidRow(
        column(
          width = 6,
          uiOutput("active_side"), 
          flipBox(
            id = "myflipbox", 
            trigger = "hover",
            width = 12,
            front = div(
              class = "text-center",
              h1("Flip on hover"),
              img(
                src = "https://image.flaticon.com/icons/svg/149/149076.svg",
                height = "300px",
                width = "100%"
              )
            ),
            back = div(
              class = "text-center",
              height = "300px",
              width = "100%",
              h1("Flip on hover"),
              p("More information....")
            )
          )
        ),
        column(
          width = 6,
          uiOutput("active_side_2"),
          flipBox(
            id = "myflipbox2",
            width = 12,
            front = div(
              class = "text-center",
              h1("Flip on click"),
              img(
                src = "https://image.flaticon.com/icons/svg/149/149076.svg",
                height = "300px",
                width = "100%"
              )
            ),
            back = div(
              class = "text-center",
              height = "300px",
              width = "100%",
              h1("Flip on click"),
              p("More information....")
            )
          )
        )
      )
    )
  ),

  server = function(input, output, session) {
    output$active_side <- renderUI({
      side <- if (input$myflipbox) "front" else "back"
      dashboardBadge(side, color = "blue")
    })

    output$active_side_2<- renderUI({
      side <- if (input$myflipbox2) "front" else "back"
      dashboardBadge(side, color = "blue")
    })
  }
 )

I'll check whether it still fail for your issue.

DivadNojnarg commented 3 years ago

I adapted your previous example. The box is rotating as expected. Notice the absence of buttons to rotate. For now, rotation only occurs on click or on hover.

remotes::install_github("RinteRface/shinydashboardPlus")
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)

shinyApp(
  ui = dashboardPage(
    dashboardHeader(),
    dashboardSidebar(),
    dashboardBody(
      fluidRow(
        column(
          width = 6,
          align = "center",
          selectInput('names', label= 'Pick a name to display the flipbox',
                      choices = c('mary', 'john', 'jane'), multiple = FALSE),
          uiOutput('flip_box')
        )
      )

    )

  ),
  server = function(input, output) {
    output$flip_box <- renderUI({

      req(input$names)

      # I need to use input values from various input widgets as parameter
      # values in flipBox, that is why I am using the renderUI
      # but the 'back side' button does not work on the second rendering
      # of the flipbox, only the first time it renders
      flipBox(
        id = 1,
        front = tagList(
          h1(input$names),
          "The first time this flipbox is displayed, the More button works fine.
        But, when you select another name from the dropdown menu, and the flipbox is re-rendered, 
        the More button does not work",
          fluidRow(
            dashboardLabel("Label 1", status = "info"),
            dashboardLabel("Label 2", status = "success"),
            dashboardLabel("Label 3", status = "warning"),
            dashboardLabel("Label 4", status = "primary"),
            dashboardLabel("Label 5", status = "danger")
          ),
          hr()
        ),
        back = tagList(
          column(
            width = 12,
            align = "center",
            sliderInput(
              "obs", 
              "Number of observations:",
              min = 0, 
              max = 1000, 
              value = 500
            )
          ),
          plotOutput("distPlot")
        ))
    })

    output$distPlot <- renderPlot({
      hist(rnorm(input$obs))
    })    

  }
)
stelmath commented 3 years ago

@DivadNojnarg I appreciate your time. Closing this, thanks!