rdataframeshinyjstreer

How to pull specific node elements from a jsTree into an R data frame?


In running the below reproducible code, I'm trying to extract specific node elements from a jsTree (using the jsTreeR package) into a data frame. Similar to what was done in related post that used sortable DnD instead of jstree at How to pull list elements from HTML/CSS and into an R data frame?

Any ideas for extracting specific node elements from a jsTree for use in a dataframe?

This is so further R operations can be performed on those dragged-in (or better said, copied over) elements.

The image at the bottom better explains.

Reproducible code (I commented out my attempts to resolve this in the below):

library(jsTreeR)
library(shiny)

nodes <- list(
  list(
    text = "Menu",
    state = list(opened = TRUE),
    children = list(
      list(text = "A", type = "moveable", state = list(disabled = TRUE)),
      list(text = "B", type = "moveable", state = list(disabled = TRUE))
    )
  ),
  list(text = "Drag here:", 
       type = "target", 
       state = list(opened = TRUE)
       )
)

checkCallback <- JS(
  "function(operation, node, parent, position, more) { console.log(node);",
  "  if(operation === 'copy_node') {",
  "    if(parent.id === '#' || node.parent !== 'j1_1' || parent.type !== 'target') {",
  "      return false;", # prevent moving an item above or below the root
  "    }",               # and moving inside an item except a 'target' item
  "  }",
  "  return true;",      # allow everything else
  "}"
)
  
dnd <- list(
  always_copy = TRUE,
  is_draggable = JS(
    "function(node) {",
    "  return node[0].type === 'moveable';",
    "}"
  )
)

ui <- fluidPage(
  tags$head(
    tags$script(
      HTML(
        script <- 
          '
            $(document).ready(function(){
              $("#mytree").on("copy_node.jstree", function(e, data){
                var instance = data.new_instance;
                var node = data.node;
                var id = node.id;
                var text = node.text;
                var index = $("#"+id).index() + 1;
                instance.rename_node(node, index + ". " + text);
              })
            });
          '
      )
    )
  ),
  
  jstreeOutput("mytree"),
  # tableOutput("table1")
  
  )  

server <- function(input, output){
  output[["mytree"]] <- renderJstree({
    jstree(
      nodes, 
      dragAndDrop = TRUE, 
      dnd = dnd, 
      checkCallback = checkCallback,
      types = list(moveable = list(), 
                   target = list()),
    )
  })

  # draggedElements <- reactive({
  #   data.frame(data = paste0(seq_along(jstreeOutput("mytree")), ". ", jstreeOutput("mytree")))
  # })
  
  # output$table1 <- renderTable({draggedElements()})
  
}  

shinyApp(ui, server)

enter image description here


Solution

  • First, unrelated to this question, I added the option inside_pos="last" in the drag-and-drop handler:

    dnd <- list(
      always_copy = TRUE,
      inside_pos = "last",
      is_draggable = JS(
        "function(node) {",
        "  return node[0].type === 'moveable';",
        "}"
      )
    )
    

    With this option, you can drop a node on the node "Drag here" and it automatically goes to the last position (see the GIF). Very convenient.

    Now, for your question. This is a job for Shiny.setInputValue. Modify the script:

    script <- '
    $(document).ready(function(){
      $("#mytree").on("copy_node.jstree", function(e, data){
        var instance = data.new_instance;
        var node = data.node;
        var id = node.id;
        var index = $("#"+id).index() + 1;
        var text = index + ". " + node.text;
        Shiny.setInputValue("choice", text);
        instance.rename_node(node, text);
      })
    });
    '
    

    And here is the Shiny app:

    ui <- fluidPage(
      tags$head(tags$script(HTML(script))),
      fluidRow(
        column(
          width = 6,
          jstreeOutput("mytree")
        ),
        column(
          width = 6,
          verbatimTextOutput("choices")
        )
      )
    )
    
    server <- function(input, output, session){
    
      output[["mytree"]] <- renderJstree(mytree)
    
      choices <- reactiveVal(data.frame(choice = character(0)))
    
      observeEvent(input[["choice"]], {
        choices(
          rbind(
            choices(),
            data.frame(choice = input[["choice"]])
          )
        )
      })
    
      output[["choices"]] <- renderPrint({
        choices()
      })
    
    }
    

    enter image description here


    EDIT: deletion

    checkCallback <- JS(
      "function(operation, node, parent, position, more) { ",
      "  if(operation === 'copy_node') {",
      "    if(parent.id === '#' || node.parent !== 'j1_1' || parent.type !== 'target') {",
      "      return false;", # prevent moving an item above or below the root
      "    }",               # and moving inside an item except a 'target' item
      "  }",
      "  if(operation === 'delete_node') {",
      "    Shiny.setInputValue('deletion', position + 1);",
      "  }",
      "  return true;",      # allow everything else
      "}"
    )
    
    server <- function(input, output, session){
    
      output[["mytree"]] <- renderJstree(mytree)
    
      Choices <- reactiveVal(data.frame(choice = character(0)))
    
      observeEvent(input[["choice"]], {
        Choices(
          rbind(
            Choices(),
            data.frame(choice = input[["choice"]])
          )
        )
      })
    
      observeEvent(input[["deletion"]], {
        Choices(
          Choices()[-input[["deletion"]], , drop = FALSE]
        )
      })
    
      output[["choices"]] <- renderPrint({
        Choices()
      })
    
    }
    

    Full app, with icons and the proton theme:

    library(jsTreeR)
    
    nodes <- list(
      list(
        text = "Menu",
        state = list(opened = TRUE),
        a_attr = list(style = "font-weight: bold;"),
        children = list(
          list(
            text = "Dog",
            type = "moveable",
            state = list(disabled = TRUE),
            icon = "fas fa-dog"
          ),
          list(
            text = "Cat",
            type = "moveable",
            state = list(disabled = TRUE),
            icon = "fas fa-cat"
          ),
          list(
            text = "Fish",
            type = "moveable",
            state = list(disabled = TRUE),
            icon = "fas fa-fish"
          )
        )
      ),
      list(
        text = ">>> Drag here <<<",
        type = "target",
        state = list(opened = TRUE),
        a_attr = list(style = "font-weight: bold;")
      )
    )
    
    checkCallback <- JS(
      "function(operation, node, parent, position, more) { ",
      "  if(operation === 'copy_node') {",
      "    if(parent.id === '#' || node.parent !== 'j1_1' || parent.type !== 'target') {",
      "      return false;", # prevent moving an item above or below the root
      "    }",               # and moving inside an item except a 'target' item
      "  }",
      "  if(operation === 'delete_node') {",
      "    Shiny.setInputValue('deletion', position + 1);",
      "  }",
      "  return true;",      # allow everything else
      "}"
    )
    
    dnd <- list(
      always_copy = TRUE,
      inside_pos = "last",
      is_draggable = JS(
        "function(node) {",
        "  return node[0].type === 'moveable';",
        "}"
      )
    )
    
    customMenu <- JS(
      "function customMenu(node) {",
      "  var tree = $('#mytree').jstree(true);", # 'mytree' is the Shiny id or the elementId
      "  var items = {",
      "    'delete' : {",
      "      'label'  : 'Delete',",
      "      'action' : function (obj) { tree.delete_node(node); },",
      "      'icon'   : 'glyphicon glyphicon-trash'",
      "     }",
      "  }",
      "  return items;",
      "}")
    
    
    mytree <- jstree(
      nodes, dragAndDrop = TRUE, dnd = dnd, checkCallback = checkCallback,
      types = list(moveable = list(), target = list()),
      contextMenu = list(items = customMenu),
      theme = "proton"
    )
    
    script <- '
    $(document).ready(function(){
      $("#mytree").on("copy_node.jstree", function(e, data){
        var instance = data.new_instance;
        var node = data.node;
        var id = node.id;
        var index = $("#"+id).index() + 1;
        var text = index + ". " + node.text;
        Shiny.setInputValue("choice", text);
        instance.rename_node(node, text);
      });
    });
    '
    
    library(shiny)
    ui <- fluidPage(
      tags$head(tags$script(HTML(script))),
      fluidRow(
        column(
          width = 4,
          jstreeOutput("mytree")
        ),
        column(
          width = 8,
          verbatimTextOutput("choices")
        )
      )
    )
    
    server <- function(input, output, session){
    
      output[["mytree"]] <- renderJstree(mytree)
    
      Choices <- reactiveVal(data.frame(choice = character(0)))
    
      observeEvent(input[["choice"]], {
        Choices(
          rbind(
            Choices(),
            data.frame(choice = input[["choice"]])
          )
        )
      })
    
      observeEvent(input[["deletion"]], {
        Choices(
          Choices()[-input[["deletion"]], , drop = FALSE]
        )
      })
    
      output[["choices"]] <- renderPrint({
        Choices()
      })
    
    }
    
    shinyApp(ui, server)
    

    enter image description here