0

I have the shiny app and the dataframes below.

When I click on a row I get a text which displays the similarity of this attribute_name with a .

If I click Next I get the similarity of the same attribute_name with the next candidate_2.

I want to be able to press the Add actionbutton() and add this candidate_2 to the related selectInput() in the table.

For example if I click on the 1st row of the table and press Add the word micro will be added in the selectInput() of the first row.

Basically what needed is to add a new row to the dataframe d everytime I select a row and press the Add actionbutton().

library(shinydashboard)
library(shinydashboardPlus)
library(DT)

attribute_name <- c("Jack", "Bob", "Jack", "Bob")
category_id <- c(7, 7, 7, 7)
candidate_phrase_lemma <- c("apple", "olive", "banana", "tomato")
d <- data.frame(
  attribute_name,
  category_id,
  candidate_phrase_lemma,
  stringsAsFactors = FALSE
)
names <- tapply(d$candidate_phrase_lemma, d$attribute_name, I)

candidate_1 <- c("Jack", "Bob", "Jack", "Bob")
candidate_2 <- c("phone", "camera", "micro", "pc")
similarity <- c(4, 5, 6, 7)
category_id <- c(7, 7, 7, 7)
e <- data.frame(candidate_1, candidate_2, similarity, category_id)

selector <- function(id, values, items = values) {
  options <- HTML(paste0(mapply(
    function(value, item) {
      as.character(tags$option(value = value, selected = "selected", item))
    }, values, items
  ), collapse = ""))
  as.character(
    tags$select(
      id = id, multiple = "multiple", options
    )
  )
}

nrows <- length(names)

initComplete <- c(
  "function(settings) {",
  sprintf("var nrows = %d;", nrows),
  "  var table = this.api().table();",
  "  function selectize(i) {",
  "    var $slct = $('#slct' + i);",
  "    $slct.select2({",
  "      width: '100%',",
  "      closeOnSelect: false",
  "    });",
  "    $slct.on('change', function(e) {",
  "      table.cell(i-1, 2).data($slct.val().length);",
  "    });",
  "  }",
  "  for(var i = 1; i <= nrows; i++) {",
  "    selectize(i);",
  "  }",
  "}"
)

js <- paste0(c(
  "Shiny.addCustomMessageHandler(",
  "  'addCandidate',",
  "  function(row_candidate) {",
  "    var i = row_candidate.row;",
  "    var candidate = row_candidate.candidate;",
  "    var $slct = $('#slct' + i);",
  "    if($slct.find(\"option[value='\" + candidate + \"']\").length === 0) {",
  "      var newOption = new Option(candidate, candidate, true, true);",
  "      $slct.append(newOption).trigger('change');",
  "    }",
  "  }",
  ");"
), collapse = "\n")

shinyApp(
  ui = dashboardPagePlus(
    tags$head(
      tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"),
      tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"),
      tags$style(HTML(
        ".select2-selection__choice {background-color: darkblue !important;}"
      )),
      tags$script(HTML(js))
    ),
    header = dashboardHeaderPlus(),
    sidebar = dashboardSidebar(),
    body = dashboardBody(
      DTOutput("table"),
      textOutput("celltext"),
      fluidRow(
        column(1, actionButton("dec", "Next")),
        column(1, actionButton("bc", "Back")),
        column(1, actionButton("addWord", "Add"))
      )
    )
  ),
  server = function(input, output, session) {
    Text <- reactiveVal()
    Data <- reactiveVal()
    Candidate <- reactiveVal()
    rnum <- reactiveVal(0)
    rnumm <- reactiveVal(0)
    
    output[["table"]] <- renderDT({
      dat <- data.frame(
        attributes = unique(as.character(d$attribute_name)),
        attributes_phrases = vapply(
          1:nrows,
          function(i) {
            selector(paste0("slct", i), names[[i]])
          },
          character(1)
        ),
        Count = lengths(names),
        stringsAsFactors = FALSE
      )
      datatable(
        data = dat,
        selection = list(target = "row", mode = "single"),
        escape = FALSE,
        rownames = FALSE,
        options = list(
          pageLength = 5,
          initComplete = JS(initComplete),
          preDrawCallback = JS(
            "function() { Shiny.unbindAll(this.api().table().node()); }"
          ),
          drawCallback = JS(
            "function() { Shiny.bindAll(this.api().table().node()); }"
          )
        )
      )
    }, server = FALSE)
    
    
    observeEvent(input[["table_rows_selected"]], {
      row <- input[["table_rows_selected"]]
      dat <- e[e[["candidate_1"]] %in% d[row, 1], ]
      Data(dat[order(dat[["similarity"]], decreasing = TRUE), ])
      Candidate(Data()[1, 2])
      Text(
        paste(
          "Similarity of", Data()[1, 1], 
          "to candidate", Candidate(), 
          "is", Data()[1, 3]
        )
      )
      rnum(1)
      rnumm(nrow(dat))
      output[["celltext"]] <- renderText({
        if (length(input[["table_rows_selected"]])) {
          Text()
        } else {
          ""
        }
      })
    })
    observeEvent(input[["dec"]], {
      if (rnum() < rnumm()) rnum(rnum() + 1)
      Candidate(Data()[rnum(), 2])
      Text(
        paste(
          "Similarity of", Data()[rnum(), 1], 
          "to candidate", Candidate(), 
          "is", Data()[rnum(), 3])
      )
    })
    observeEvent(input[["bc"]], {
      if (rnum() < rnumm()) rnum(rnum() - 1)
      Candidate(Data()[rnum(), 2])
      Text(
        paste(
          "Similarity of", Data()[rnum(), 1], 
          "to candidate", Candidate(), 
          "is", Data()[rnum(), 3])
      )
    })
    observeEvent(input[["addWord"]], {
      session$sendCustomMessage(
        "addCandidate",
        list(row = input[["table_rows_selected"]], candidate = Candidate())
      )
    })
  }
)
firmo23
  • 7,490
  • 2
  • 38
  • 114

1 Answers1

1

Nice app. Below is my solution. I don't add a new row to the dataframe; I directly add an item to the select input via JavaScript.

enter image description here

library(shinydashboard)
library(shinydashboardPlus)
library(DT)

attribute_name <- c("Jack", "Bob", "Jack", "Bob")
category_id <- c(7, 7, 7, 7)
candidate_phrase_lemma <- c("apple", "olive", "banana", "tomato")
d <- data.frame(
  attribute_name,
  category_id,
  candidate_phrase_lemma,
  stringsAsFactors = FALSE
)
names <- tapply(d$candidate_phrase_lemma, d$attribute_name, I)

candidate_1 <- c("Jack", "Bob", "Jack", "Bob")
candidate_2 <- c("phone", "camera", "micro", "pc")
similarity <- c(4, 5, 6, 7)
category_id <- c(7, 7, 7, 7)
e <- data.frame(candidate_1, candidate_2, similarity, category_id)

selector <- function(id, values, items = values) {
  options <- HTML(paste0(mapply(
    function(value, item) {
      as.character(tags$option(value = value, selected = "selected", item))
    }, values, items
  ), collapse = ""))
  as.character(
    tags$select(
      id = id, multiple = "multiple", options
    )
  )
}

nrows <- length(names)

initComplete <- c(
  "function(settings) {",
  sprintf("var nrows = %d;", nrows),
  "  var table = this.api().table();",
  "  function selectize(i) {",
  "    var $slct = $('#slct' + i);",
  "    $slct.select2({",
  "      width: '100%',",
  "      closeOnSelect: false",
  "    });",
  "    $slct.on('change', function(e) {",
  "      table.cell(i-1, 2).data($slct.val().length);",
  "    });",
  "  }",
  "  for(var i = 1; i <= nrows; i++) {",
  "    selectize(i);",
  "  }",
  "}"
)

js <- paste0(c(
  "Shiny.addCustomMessageHandler(",
  "  'addCandidate',",
  "  function(row_candidate) {",
  "    var i = row_candidate.row;",
  "    var candidate = row_candidate.candidate;",
  "    var $slct = $('#slct' + i);",
  "    if($slct.find(\"option[value='\" + candidate + \"']\").length === 0) {",
  "      var newOption = new Option(candidate, candidate, true, true);",
  "      $slct.append(newOption).trigger('change');",
  "    }",
  "  }",
  ");"
), collapse = "\n")

shinyApp(
  ui = dashboardPagePlus(
    tags$head(
      tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"),
      tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"),
      tags$style(HTML(
        ".select2-selection__choice {background-color: darkblue !important;}"
      )),
      tags$script(HTML(js))
    ),
    header = dashboardHeaderPlus(),
    sidebar = dashboardSidebar(),
    body = dashboardBody(
      DTOutput("table"),
      textOutput("celltext"),
      fluidRow(
        column(1, actionButton("dec", "Next")),
        column(1, actionButton("addWord", "Add"))
      )
    )
  ),
  server = function(input, output, session) {
    Text <- reactiveVal()
    Data <- reactiveVal()
    Candidate <- reactiveVal()
    rnum <- reactiveVal(0)
    rnumm <- reactiveVal(0)
    
    output[["table"]] <- renderDT({
      dat <- data.frame(
        attributes = unique(as.character(d$attribute_name)),
        attributes_phrases = vapply(
          1:nrows,
          function(i) {
            selector(paste0("slct", i), names[[i]])
          },
          character(1)
        ),
        Count = lengths(names),
        stringsAsFactors = FALSE
      )
      datatable(
        data = dat,
        selection = list(target = "row", mode = "single"),
        escape = FALSE,
        rownames = FALSE,
        options = list(
          pageLength = 5,
          initComplete = JS(initComplete),
          preDrawCallback = JS(
            "function() { Shiny.unbindAll(this.api().table().node()); }"
          ),
          drawCallback = JS(
            "function() { Shiny.bindAll(this.api().table().node()); }"
          )
        )
      )
    }, server = FALSE)
    
    
    observeEvent(input[["table_rows_selected"]], {
      row <- input[["table_rows_selected"]]
      dat <- e[e[["candidate_1"]] %in% d[row, 1], ]
      Data(dat[order(dat[["similarity"]], decreasing = TRUE), ])
      Candidate(Data()[1, 2])
      Text(
        paste(
          "Similarity of", Data()[1, 1], 
          "to candidate", Candidate(), 
          "is", Data()[1, 3]
        )
      )
      rnum(1)
      rnumm(nrow(dat))
      output[["celltext"]] <- renderText({
        if (length(input[["table_rows_selected"]])) {
          Text()
        } else {
          ""
        }
      })
    })
    observeEvent(input[["dec"]], {
      if (rnum() < rnumm()) rnum(rnum() + 1)
      Candidate(Data()[rnum(), 2])
      Text(
        paste(
          "Similarity of", Data()[rnum(), 1], 
          "to candidate", Candidate(), 
          "is", Data()[rnum(), 3])
      )
    })
    observeEvent(input[["addWord"]], {
      session$sendCustomMessage(
        "addCandidate",
        list(row = input[["table_rows_selected"]], candidate = Candidate())
      )
    })
  }
)

EDIT: styling suggestion

enter image description here

shinyApp(
  ui = dashboardPagePlus(
    tags$head(
      tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"),
      tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"),
      tags$style(HTML(
        ".select2-selection__choice {background-color: darkblue !important;}"
      )),
      tags$script(HTML(js))
    ),
    header = dashboardHeaderPlus(),
    sidebar = dashboardSidebar(),
    body = dashboardBody(
      DTOutput("table"),
      conditionalPanel(
        condition = "input.table_rows_selected.length > 0",
        wellPanel(
          uiOutput("celltext"),
          splitLayout(
          actionButton("dec", "Next candidate"),
          actionButton("addWord", "Add this candidate"),
          cellWidths = "fit-content"
        )
        )
      )
    )
  ),
  server = function(input, output, session) {
    Text <- reactiveVal()
    Data <- reactiveVal()
    Candidate <- reactiveVal()
    rnum <- reactiveVal(0)
    rnumm <- reactiveVal(0)
    
    output[["table"]] <- renderDT({
      dat <- data.frame(
        attributes = unique(as.character(d$attribute_name)),
        attributes_phrases = vapply(
          1:nrows,
          function(i) {
            selector(paste0("slct", i), names[[i]])
          },
          character(1)
        ),
        Count = lengths(names),
        stringsAsFactors = FALSE
      )
      datatable(
        data = dat,
        selection = list(target = "row", mode = "single"),
        escape = FALSE,
        rownames = FALSE,
        options = list(
          pageLength = 5,
          initComplete = JS(initComplete),
          preDrawCallback = JS(
            "function() { Shiny.unbindAll(this.api().table().node()); }"
          ),
          drawCallback = JS(
            "function() { Shiny.bindAll(this.api().table().node()); }"
          )
        )
      )
    }, server = FALSE)
    
    
    observeEvent(input[["table_rows_selected"]], {
      row <- input[["table_rows_selected"]]
      dat <- e[e[["candidate_1"]] %in% d[row, 1], ]
      Data(dat[order(dat[["similarity"]], decreasing = TRUE), ])
      Candidate(Data()[1, 2])
      Text(
        paste(
          "Similarity of <em>", Data()[1, 1], "</em>", 
          "to candidate <em>", Candidate(), "</em>", 
          "is <strong>", Data()[1, 3], "</strong>"
        )
      )
      rnum(1)
      rnumm(nrow(dat))
      output[["celltext"]] <- renderUI({
        if (length(input[["table_rows_selected"]])) {
          HTML(Text())
        } else {
          ""
        }
      })
    })
    observeEvent(input[["dec"]], {
      if (rnum() < rnumm()) rnum(rnum() + 1)
      Candidate(Data()[rnum(), 2])
      Text(
        paste(
          "Similarity of <em>", Data()[rnum(), 1], "</em>", 
          "to candidate <em>", Candidate(), "</em>",
          "is <strong>", Data()[rnum(), 3], "</strong>"
        )
      )
    })
    observeEvent(input[["addWord"]], {
      session$sendCustomMessage(
        "addCandidate",
        list(row = input[["table_rows_selected"]], candidate = Candidate())
      )
    })
  }
)

EDIT

Regarding your comment, here is the app with a simplified server logic and the back/next buttons are disabled when needed:

library(shinyjs)

shinyApp(
  ui = dashboardPagePlus(
    tags$head(
      tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css"),
      tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"),
      tags$style(HTML(
        ".select2-selection__choice {background-color: darkblue !important;}"
      )),
      tags$script(HTML(js))
    ),
    useShinyjs(),
    header = dashboardHeaderPlus(),
    sidebar = dashboardSidebar(),
    body = dashboardBody(
      DTOutput("table"),
      conditionalPanel(
        condition = "input.table_rows_selected.length > 0",
        wellPanel(
          uiOutput("celltext"),
          splitLayout(
            actionButton("bc", "Previous candidate"),
            actionButton("dec", "Next candidate"),
            actionButton("addWord", "Add this candidate", class = "btn-info"),
            cellWidths = "fit-content"
          )
        )
      )
    )
  ),
  server = function(input, output, session) {
    Text <- reactiveVal()
    Data <- reactiveVal()
    Candidate <- reactiveVal()
    rnum <- reactiveVal()

    output[["table"]] <- renderDT({
      dat <- data.frame(
        attributes = unique(as.character(d$attribute_name)),
        attributes_phrases = vapply(
          1:nrows,
          function(i) {
            selector(paste0("slct", i), names[[i]])
          },
          character(1)
        ),
        Count = lengths(names),
        stringsAsFactors = FALSE
      )
      datatable(
        data = dat,
        selection = list(target = "row", mode = "single"),
        escape = FALSE,
        rownames = FALSE,
        options = list(
          pageLength = 5,
          initComplete = JS(initComplete),
          preDrawCallback = JS(
            "function() { Shiny.unbindAll(this.api().table().node()); }"
          ),
          drawCallback = JS(
            "function() { Shiny.bindAll(this.api().table().node()); }"
          )
        )
      )
    }, server = FALSE)
    
    
    observeEvent(input[["table_rows_selected"]], {
      row <- input[["table_rows_selected"]]
      dat <- e[e[["candidate_1"]] %in% d[row, 1], ]
      Data(dat[order(dat[["similarity"]], decreasing = TRUE), ])
      rnum(1)
    })
    output[["celltext"]] <- renderUI({
      HTML(Text())
    })
    observeEvent(input[["dec"]], {
      rnum(rnum() + 1)
    })
    observeEvent(input[["bc"]], {
      rnum(rnum() - 1)
    })
    observeEvent(list(rnum(), Data()), {
      if(rnum() == 1){
        disable("bc")
      }else{
        enable("bc")
      }
      if(rnum() == nrows){
        disable("dec")
      }else{
        enable("dec")
      }
      Candidate(Data()[rnum(), 2])
      Text(
        paste(
          "Similarity of <em>", Data()[rnum(), 1], "</em>",
          "to candidate <em>", Candidate(), "</em>",
          "is <strong>", Data()[rnum(), 3], "</strong>"
        )
      )
    }, ignoreInit = TRUE)
    observeEvent(input[["addWord"]], {
      session$sendCustomMessage(
        "addCandidate",
        list(row = input[["table_rows_selected"]], candidate = Candidate())
      )
    })
  }
)
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • Thank you a lot, just a small detail. Besides the Next button I also had a Back button which was tracking the previous word. I had not put it in order to provide minimal code. The issue with that is that now when there is no previous word it keeps going back while normally it should stop when its in the 1st word and display the last valid message. The same logic was applied for the Next button as well when it reaches the last word. I edited based on your solution. – firmo23 Jan 13 '21 at 03:32
  • I get an error Error in FUN(X[[i]], ...) : "fit-content" is not a valid CSS unit (e.g., "100%", "400px", "auto") Could you adapt to the version that I used in my edit cause that was the working one. – firmo23 Jan 13 '21 at 04:59
  • 1
    @firmo23 This is a new feature of the `htmltools` package - you have to upgrade it. – Stéphane Laurent Jan 13 '21 at 05:10
  • I have htmltools_0.5.1 installed but still getting Error in FUN(X[[i]], ...) : argument is not a character vector – firmo23 Jan 13 '21 at 05:45
  • @firmo23 Hmm.. This error is possibly thrown by the `HTML` function when `Text()` is `NULL` (I have the development version of `htmltools` prior to 0.5.1). This is a guess. Could you try to replace `Text <- reactiveVal()` with `Text <- reactiveVal("")`. – Stéphane Laurent Jan 13 '21 at 06:00
  • I have replaced all reactiveVal() with reactiveVal("") but the error still exists – firmo23 Jan 13 '21 at 12:59
  • ok I got this but based on your non stylish solution thanks – firmo23 Jan 13 '21 at 21:19
  • I added a new button based on your solution here could you check? https://stackoverflow.com/questions/65710169/assign-a-value-on-a-different-row-of-a-datatable-than-the-clicked-one-in-a-shiny – firmo23 Jan 13 '21 at 21:41
  • and a different one here as well. https://stackoverflow.com/questions/65712967/move-to-the-next-row-of-clicked-one-in-a-dt-datatable-using-an-actionbutton – firmo23 Jan 14 '21 at 03:23