Giter Site home page Giter Site logo

jstreer's Introduction

jsTreeR

R-CMD-check

A wrapper of the JavaScript library jsTree. This package is similar to shinyTree but it allows more options. It also provides a Shiny gadget allowing to manipulate one or more folders.

Installation

Install from CRAN:

install.packages("jsTreeR")

Or install the latest development version (on GitHub):

remotes::install_github("stla/jsTreeR")

Getting started

Please check the Shiny examples (see ?jstreeExamples).

The 'folder gadget':

The 'tree navigator' Shiny module:

The 'tree navigator' has not all the features of the 'folder gadget', it only allows to navigate in the server side file system and to select some files. But the 'folder gadget' loads all the structure of the root folder(s), while the 'tree navigator' loads the contents of a clicked folder only when this one is clicked by the user. And as a Shiny module, it is possible to build around it a more elaborated Shiny app.


Copies of license agreements

The 'jsTreeR' package as a whole is distributed under GPL-3 (GNU GENERAL PUBLIC LICENSE version 3).

It includes other open source software components. The following is a list of these components:

Full copies of the license agreements used by these components are included in the file LICENSE.note.

jstreer's People

Contributors

stla avatar stlagsk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

jstreer's Issues

input[["mytree_full"]] is not updated on expanding a node

I'd like to save the state of a jstree, so the user has the possibility to come back later and continue to work on it.

Currently the full input state (e.g. input[["mytree_full"]]) is not updated on expanding a node. However, it is updated when adding a child node via the contextMenu:

library(jsTreeR)
library(shiny)

ui <- fluidPage(
  textInput("test", "test"),
  jstreeOutput("mytree"),
  textOutput("opened")
)

server <- function(input, output, session){
  output[["mytree"]] <- renderJstree({
    # jstreeDestroy(session, id = "mytree")
    nodes <- list(
      list(
        text = paste0(input$test, "Branch 1"),
        state = list(opened = TRUE, disabled = FALSE),
        type = "parent",
        children = list(
          list(text = "Leaf A", type = "child"),
          list(text = "Leaf B", type = "child"),
          list(text = "Leaf C", type = "child"),
          list(text = "Leaf D", type = "child")
        )
      ),
      list(text = "Branch 2", type = "parent", state = list(opened = TRUE))
    )
    jstree(nodes, contextMenu = TRUE)
  })
  
  output$opened <- renderText({
    if(isTruthy(input[["mytree_full"]])){
      paste("Is node 1 opened?:", input[["mytree_full"]][[1]]$state$opened)
    }
  })
}  

shinyApp(ui, server)

screen

How to edit the contextMenu in the jstree?

library(jsTreeR)

nodes <- list(
  list(
    text = "RootA",
    type = "root",
    children = list(
      list(
        text = "ChildA1",
        type = "child"
      ),
      list(
        text = "ChildA2",
        type = "child"
      )
    )
  ),
  list(
    text = "RootB",
    type = "root",
    children = list(
      list(
        text = "ChildB1",
        type = "child"
      ),
      list(
        text = "ChildB2",
        type = "child"
      )
    )
  )
)

nodes %>% jstree(contextMenu = TRUE)

I tried:

nodes %>% jstree(contextMenu = list(Rename = TRUE))

But this won't work. I assume you need to pass in the javascript for the corresponding callBack function? Is there any documentation how how to pass this information through? Ideally I could just have the callback function loaded in an external js script.

Thanks again for building this widget. Fits my use case perfectly, and shinyTree wasn't working well.

jsTreeR affects other htmlwidgets' styling

After switching from shinyTree to jsTreeR, all other htmlwidgets appear to have their styling set to those of jsTree when they are rendered through renderUI.

As exemplified below, a shinyWidgets::radioGroupButtons element is used:

  1. when using shinyTree, the following rendering is obtained (desirable):
    Screenshot 2024-05-05 215821

  2. after switching to jsTreeR, the following rendering is obtained (undesirable):
    Screenshot 2024-05-05 215745

Below is a reprex, firstly using shinyTree, where the renderings are good in both "shinyTree" and "shinyTree uiOutput" tabs:

library(shiny)
library(bslib)
library(shinyWidgets)
library(shinyTree)
library(jsTreeR)

# using shinyTree
nodes_shinyTree<-list(foo=list(bar=list()))

shinyTree_ui<-card(
  shinyTree('shinyTree_tree_ui'),
  radioGroupButtons('rgb1',choices=c('A','B'),size='sm'))

ui_shinyTree<-page_navbar(
  title='shinyTree',
  
  nav_panel(
    title='shinyTree',
    card(
      shinyTree('shinyTree_tree'),
      radioGroupButtons('rgb',choices=c('A','B'),size='sm')
    )
  ),

  nav_panel(
    title='shinyTree uiOutput',
    uiOutput('shinyTree_ui')
  )
  
)

server_shinyTree<-function(input,output,session){
  output$shinyTree_tree<-renderTree(nodes_shinyTree)
  
  output$shinyTree_tree_ui<-renderTree(nodes_shinyTree)
  output$shinyTree_ui<-renderUI(shinyTree_ui)
  
  
}

shinyApp(ui_shinyTree,server_shinyTree)

Same example, now using jsTreeR instead of shinyTree; notice the problem in the "jsTreeR uiOutput" tab:

library(shiny)
library(bslib)
library(shinyWidgets)
library(shinyTree)
library(jsTreeR)


# using jsTreeR
nodes_jsTreeR<-list(list(text='foo',children=list(list(text='bar'))))

jsTreeR_ui<-card(
    jstreeOutput('jstree_tree_ui'),
    radioGroupButtons('rgb1',choices=c('A','B'),size='sm'))


ui_jsTreeR<-page_navbar(
  title='jsTreeR',
  
  nav_panel(
    title='jsTreeR',
    card(
      jstreeOutput('jstree_tree'),
      radioGroupButtons('rgb',choices=c('A','B'),size='sm')
    )
  ),

  nav_panel(
    title='jsTreeR uiOutput',
    uiOutput('jsTreeR_ui')
  )
)

server_jsTreeR<-function(input,output,session){
  output$jstree_tree<-renderJstree(jstree(nodes_jsTreeR))
  
  output$jsTreeR_ui<-renderUI(jsTreeR_ui)
  output$jstree_tree_ui<-renderJstree(jstree(nodes_jsTreeR))
  
}

shinyApp(ui_jsTreeR,server_jsTreeR)

Are there any examples of jsTreeR where the user can save/download a tree structure and later retrieve it/upload it into an active session?

I have built an App using jsTreeR where the tree structure the user creates is reflected in a mirror dataframe, and that dataframe then drives a series of mathematical operations. The user can create very elaborate trees with multiple branches. It would be helpful if the user could download or "save" a tree, naming each saved tree; and later upload or retrieve a tree to continue working on it or to simply activate that tree in order to run that mathematical operation. I could see a user needing a "library" of trees. Does jsTreeR have this capability? Are there any examples of this having been done?

I'm open to any suggestions. The image gives an example of "saving a tree"
Image1
.

Error when using custom contextMenu

Dear Stéphane,

running the below app (which more or less is a copy of the contextMenu example) via the latest CRAN version of jsTreeR gives me the following error:

image

This seems to be related to the custom contextMenu. Setting contextMenu = TRUE does not result in an error.

library(shiny)
library(jsTreeR)

customContextMenu <- JS("function customMenu(node)
{
  var tree = $('#mytree').jstree(true);
  var items = {
    'rename' : {
      'label' : 'Rename',
      'action' : function (obj) { tree.edit(node); },
      'icon': 'glyphicon glyphicon-edit'
    },
    'delete' : {
      'label' : 'Delete',
      'action' : function (obj) { tree.delete_node(node); },
      'icon' : 'glyphicon glyphicon-trash'
    },
    'create' : {
      'label' : 'Create',
      'action' : function (obj) { tree.create_node(node); },
      'icon': 'glyphicon glyphicon-plus'
    }
  }
  return items;
}")

nodes <- list(
  list(
    text = "RootA",
    children = list(
      list(
        text = "ChildA1"
      ),
      list(
        text = "ChildA2"
      )
    )
  ),
  list(
    text = "RootB",
    children = list(
      list(
        text = "ChildB1"
      ),
      list(
        text = "ChildB2"
      )
    )
  )
)

ui <- fluidPage(
  jstreeOutput("mytree")
)

server <- function(input, output, session) {
  output[["mytree"]] <- renderJstree({
    jstree(nodes, contextMenu = list(items = customContextMenu))
  })
}

shinyApp(ui, server)

> sessionInfo()
R version 4.3.2 (2023-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)

Matrix products: default

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] jsTreeR_2.5.0 shiny_1.8.0  

loaded via a namespace (and not attached):
 [1] crayon_1.5.2      cli_3.6.2         rlang_1.1.2       shinyAce_0.4.2    promises_1.2.1    jsonlite_1.8.8    xtable_1.8-4     
 [8] htmltools_0.5.7   httpuv_1.6.13     sass_0.4.8        jquerylib_0.1.4   ellipsis_0.3.2    fontawesome_0.5.2 fastmap_1.1.1    
[15] base64enc_0.1-3   yaml_2.3.8        lifecycle_1.0.4   memoise_2.0.1     compiler_4.3.2    miniUI_0.1.1.1    htmlwidgets_1.6.4
[22] Rcpp_1.0.11       rstudioapi_0.15.0 later_1.3.2       R.oo_1.25.0       R.utils_2.12.3    digest_0.6.33     R6_2.5.1         
[29] magrittr_2.0.3    bslib_0.6.1       R.methodsS3_1.8.2 withr_2.5.2       tools_4.3.2       mime_0.12         cachem_1.0.8   

stack trace:

Listening on http://127.0.0.1:4519
Populating tree using a list.
Warning: Error in ||: invalid 'y' type in 'x || y'
  103: h
  102: .handleSimpleError
  101: %||%
  100: jstree
   99: shinyRenderWidget [#3]
   98: func
   97: force
   96: withVisible
   95: withCallingHandlers
   94: domain$wrapSync
   93: promises::with_promise_domain
   92: captureStackTraces
   91: doTryCatch
   90: tryCatchOne
   89: tryCatchList
   88: tryCatch
   87: do
   86: hybrid_chain
   85: renderFunc
   84: output$mytree
   83: ..stacktraceon..
   82: orig
   81: func
   80: withCallingHandlers
   79: domain$wrapSync
   78: promises::with_promise_domain
   77: captureStackTraces
   76: withCallingHandlers
   75: shinyCallingHandlers
   74: force
   73: domain$wrapSync
   72: promises::with_promise_domain
   71: private$withCurrentOutput
   70: force
   69: withVisible
   68: withCallingHandlers
   67: domain$wrapSync
   66: promises::with_promise_domain
   65: captureStackTraces
   64: doTryCatch
   63: tryCatchOne
   62: tryCatchList
   61: tryCatch
   60: do
   59: hybrid_chain
   58: force
   57: withVisible
   56: withCallingHandlers
   55: domain$wrapSync
   54: promises::with_promise_domain
   53: captureStackTraces
   52: doTryCatch
   51: tryCatchOne
   50: tryCatchList
   49: tryCatch
   48: do
   47: hybrid_chain
   46: observe
   45: <observer:output$mytree>
   44: contextFunc
   43: env$runWith
   42: force
   41: domain$wrapSync
   40: promises::with_promise_domain
   39: withReactiveDomain
   38: domain$wrapSync
   37: promises::with_promise_domain
   36: ctx$run
   35: run
   34: withCallingHandlers
   33: domain$wrapSync
   32: promises::with_promise_domain
   31: captureStackTraces
   30: withCallingHandlers
   29: shinyCallingHandlers
   28: force
   27: withVisible
   26: withCallingHandlers
   25: domain$wrapSync
   24: promises::with_promise_domain
   23: captureStackTraces
   22: doTryCatch
   21: tryCatchOne
   20: tryCatchList
   19: tryCatch
   18: do
   17: hybrid_chain
   16: flushCallback
   15: FUN
   14: lapply
   13: ctx$executeFlushCallbacks
   12: .getReactiveEnvironment()$flush
   11: flushReact
   10: serviceApp
    9: ..stacktracefloor..
    8: withCallingHandlers
    7: domain$wrapSync
    6: promises::with_promise_domain
    5: captureStackTraces
    4: ..stacktraceoff..
    3: runApp
    2: print.shiny.appobj
    1: <Anonymous>

jsTree not updating when using `uiOutput()`

Here is a minimal example of the behavior. If the jstree output is created using renderUI() in the server, the updating of the tree does not work as expected. (Sidenote: I do not know why the second Root folder is not showing in my example either.)

library(shiny)
library(jsTreeR)

nodes1 <- list(
  list(
    text = "Root1A",
    children = list(
      list(text = "Child1A1")
    )
  ),
  list(
    text = "Root1B",
    children = list(
      list(text = "Child1B1"),
      list(text = "Child1B2")
    )
  )
)

nodes2 <- list(
  list(
    text = "Root2A",
    children = list(
      list(text = "Child2A1"),
      list(text = "Child2A2")
    )
  ),
  list(
    text = "Root2B",
    children = list(
      list(text = "Child2B1")
    )
  )
)

ui <- fluidPage(
  selectInput("node_select", "Select Node List", list("Node 1" = "n1", "Node 2" = "n2")),
  jstreeOutput("dirtree1"),
  uiOutput("dirtree2_ui")
)

server <- function(input, output, session) {
  nodes <- reactiveVal(list())
  
  observeEvent(input$node_select, nodes(ifelse(input$node_select == "n1", nodes1, nodes2)))
  
  output$dirtree2_ui <- renderUI({
    tagList(
      h2(paste("You have selected", input$node_select)),
      jstreeOutput("dirtree2")
    )
  })
  
  output$dirtree1 <- renderJstree(jstree(isolate(nodes())))
  output$dirtree2 <- renderJstree(jstree(isolate(nodes())))
  
  observeEvent(nodes(), {
    jsTreeR::jstreeUpdate(session, "dirtree1", nodes())
    jsTreeR::jstreeUpdate(session, "dirtree2", nodes())
  }, ignoreInit = TRUE)
}

shinyApp(ui, server)

BDDC2F2F-CD86-4127-8011-5A9B683DC332

FR: tie_selection setting

I'd like to try using the tie_selection setting for the checkbox plugin as shown here. Currently the checkboxes argument for jstree() is logical.

I tried using htmlwidgets::onRender to set it, however, the behaviour doesn't change. The checkbox is still checked/unchecked once the text is clicked:

library(shiny)
library(jsTreeR)

nodes <- list(
  list(
    text = "RootA",
    children = list(
      list(
        text = "ChildA1"
      ),
      list(
        text = "ChildA2"
      )
    )
  ),
  list(
    text = "RootB",
    children = list(
      list(
        text = "ChildB1"
      ),
      list(
        text = "ChildB2"
      )
    )
  )
)

ui <- fluidPage(
  jstreeOutput("myTree")
)

server <- function(input, output, session){
  output[["myTree"]] <- renderJstree({
    suppressMessages(
      jstree(
        nodes,
        checkboxes=TRUE,
        dragAndDrop = TRUE
      ) |> htmlwidgets::onRender("function(el) {console.log(el); $(el).jstree({checkbox : { tie_selection : false }}); console.log(el);}"))
  })
}  

shinyApp(ui, server)

Error in browser console when using the grid parameter

Running the below app using the latest jsTreeR dev version and the latest shiny cran version gives me:

image

library(jsTreeR)
library(shiny)

nodes <- list(
  list(
    text = "Fruits",
    type = "fruit",
    icon = "supertinyicon-transparent-raspberry_pi",
    a_attr = list(class = "helvetica"),
    children = list(
      list(
        text = "Apple",
        type = "fruit",
        data = list(
          price = 0.1,
          quantity = 20,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Banana",
        type = "fruit",
        data = list(
          price = 0.2,
          quantity = 31,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Grapes",
        type = "fruit",
        data = list(
          price = 1.99,
          quantity = 34,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Mango",
        type = "fruit",
        data = list(
          price = 0.5,
          quantity = 8,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Melon",
        type = "fruit",
        data = list(
          price = 0.8,
          quantity = 4,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Pear",
        type = "fruit",
        data = list(
          price = 0.1,
          quantity = 30,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Strawberry",
        type = "fruit",
        data = list(
          price = 0.15,
          quantity = 32,
          cssclass = "lightorange"
        )
      )
    ),
    state = list(
      opened = TRUE
    )
  ),
  list(
    text = "Vegetables",
    type = "vegetable",
    icon = "supertinyicon-transparent-vegetarian",
    a_attr = list(class = "helvetica"),
    children = list(
      list(
        text = "Aubergine",
        type = "vegetable",
        data = list(
          price = 0.5,
          quantity = 8,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Broccoli",
        type = "vegetable",
        data = list(
          price = 0.4,
          quantity = 22,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Carrot",
        type = "vegetable",
        data = list(
          price = 0.1,
          quantity = 32,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Cauliflower",
        type = "vegetable",
        data = list(
          price = 0.45,
          quantity = 18,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Potato",
        type = "vegetable",
        data = list(
          price = 0.2,
          quantity = 38,
          cssclass = "lightgreen"
        )
      )
    )
  )
)

grid <- list(
  columns = list(
    list(
      width = 200,
      header = "Product",
      headerClass = "bolditalic yellow centered",
      wideValueClass = "cssclass"
    ),
    list(
      width = 150,
      value = "price",
      header = "Price",
      wideValueClass = "cssclass",
      headerClass = "bolditalic yellow centered",
      wideCellClass = "centered"
    ),
    list(
      width = 150,
      value = "quantity",
      header = "Quantity",
      wideValueClass = "cssclass",
      headerClass = "bolditalic yellow centered",
      wideCellClass = "centered"
    )
  ),
  width = 600
)

types <- list(
  fruit = list(
    a_attr = list(
      class = "lightorange"
    ),
    icon = "supertinyicon-transparent-symantec"
  ),
  vegetable = list(
    a_attr = list(
      class = "lightgreen"
    ),
    icon = "supertinyicon-transparent-symantec"
  )
)

ui <-   fluidPage(
  tags$head(
    tags$style(
      HTML(c(
        ".lightorange {background-color: #fed8b1;}",
        ".lightgreen {background-color: #98ff98;}",
        ".bolditalic {font-weight: bold; font-style: italic; font-size: large;}",
        ".yellow {background-color: yellow !important;}",
        ".centered {text-align: center; font-family: cursive;}",
        ".helvetica {font-weight: 700; font-family: Helvetica; font-size: larger;}"
      ))
    )
  ),
  titlePanel("jsTree grid"),
  jstreeOutput("jstree")
)

server <-   function(input, output){
  output[["jstree"]] <-
    renderJstree(jstree(nodes, search = TRUE, grid = grid, types = types))
}

shinyApp(ui, server)

Get selected nodes

Hi!

Is there a way to extract the selected nodes of the tree?
[similar to shinyTree::get_selected()]

Thanks

input[["mytree_full"]] is not updated on renaming a node

Hi Stéphane,

I just realized, that renaming a node has no effect on the shiny input - the text changes only after e.g. adding a new node:

screen

It would be great if this event could be added.

Example app:

library(jsTreeR)
library(shiny)
library(jsonlite)

nodes <- list(
  list(
    text = "Branch 1",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE,
      undetermined = TRUE
    ),
    type = "parent",
    children = list(
      list(
        text = "Leaf A",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE,
          undetermined = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf B",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE,
          undetermined = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf C",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = TRUE,
          undetermined = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf D",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = TRUE,
          undetermined = FALSE
        ),
        type = "child"
      )
    )
  ),
  list(
    text = "Branch 2",
    type = "parent",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE,
      checked = TRUE,
      undetermined = FALSE
    )
  )
)

ui <- fluidPage(jstreeOutput("mytree"),  verbatimTextOutput("mytree_full"))

server <- function(input, output, session) {
  output[["mytree"]] <- renderJstree({
    jstree(nodes, contextMenu = TRUE, checkboxes = TRUE, checkWithText = FALSE)
  })
  
  output$mytree_full <-  renderPrint({toJSON(input$mytree_full, pretty = TRUE)})
}



shinyApp(ui, server)

jsTreeDeleted Event not recorded

First of all thank for fixing my last issue very quickly ! Now, when developing new functionalities for my app, I ran into a similar problem as in my last issue #28 When I first delete a node then put it back again the following deletion events are not recorded.
The fix is probably the same as in the last issue : adding {priority: "event"} in the equivalent of jsTreeCopied but for jsTreeDeleted. I also suspect that I will run into the same for jsTreeMoved event so adding it there might also make sense

Best regards,
Thanks

Reordering node under root

I got some issues while moving nodes right under the root node. Mostly the concat function not working here (on jstreer.js at $el.on("move_node.jstree", function(e, data)) :

var oldPath = oldInstance
              .get_path(data.old_parent)
              .concat(nodeText);

Exemple tree :

# (root node)
- A (child node)
- B
- C

I figured out a solution that would improve the consistancy of the jsTreeMoved event of the "from" and the "to" values while also retrieve the positions of the nodes (which is important in my use case but might also be helpful to other users) :
on $el.on("move_node.jstree", function(e, data) :

Shiny.setInputValue("jsTreeMoved:jsTreeR.copied", {
              from: { instance: oldInstanceId, path: oldPath },
              to: { instance: newInstanceId, path: newPath }
            }, {priority: "event"});

can be replaced by either :

Shiny.setInputValue("jsTreeMoved:jsTreeR.copied",{
              from: {
                instance: oldInstanceId,
                parent: data.old_parent,
                path: oldInstance.get_path(node),
                position: data.old_position},
              to: { 
                instance: newInstanceId,
                parent: data.parent,
                path: newInstance.get_path(node),
                position: data.position}
            }, {priority: "event"});

or :

Shiny.setInputValue("jsTreeMoved:jsTreeR.copied"{
              from: {
                instance: oldInstanceId,
                path: [data.old_parent].concat(oldInstance.get_path(node)),
                position: data.old_position},
              to: { 
                instance: newInstanceId,
                path: [data.parent].concat(newInstance.get_path(node)),
                position: data.position}
            }, {priority: "event"});

Thank for looking into this issue !
Best regards

Search does not show childs

Hi, in shinyTree when you search for something it shows the childs of a node matching the search:
shinytree

But as far as I can see when jsTreeR does not:
jsTreeR

Lazy loading nodes no longer works.

In v.1.2.0, we used to be able to pass an htmlwidgets::JS() object as the 'nodes' parameter to jsTreeR::jstree(node).
This would allow us to setup AJAX remote node loading via callback function().

Now you have a check that forces 'nodes' to be a node list:

https://github.com/stla/jsTreeR/blob/master/R/jstree.R#L381

and AJAX/lazy node loading via callback function is now broken:

https://github.com/vakata/jstree/wiki#populating-the-tree-using-a-callback-function

Is the "isNodesList()" critical for you, or can you remove that to re-enable ajax callbacks?
Or can you use your isJS() function and allow either:

if(!isNodesList(nodes) && !isJS(nodes)) {
stop()
}

For reference, I implemented the AJAX callback using guidance from Dean Attali's notes here:

https://github.com/daattali/advanced-shiny/tree/master/api-ajax

overlapping nodes

I guess this is an issue with the underlying js library, however, coming from here:

Using the below hierarchy in some cases nodes are overlapping after being opened:

screen

library(jsTreeR)

nodes <- list(
  list(
    text = "RootA",
    type = "root",
    children = list(
      list(
        text = "ChildA1",
        type = "child",
        children = list(list(text = "ChildA12",
                             type = "child"))
      ),
      list(text = "ChildA2",
           type = "child")
    )
  ),
  list(
    text = "RootB",
    type = "root",
    children = list(
      list(text = "ChildB1",
           type = "child"),
      list(text = "ChildB2",
           type = "child")
    )
  )
)

types <- list(root = list(icon = FALSE),
              child = list(icon = FALSE))

jstree(nodes,
       types = types,
       checkboxes = TRUE)

jsTreeR disables reactive callbacks from bslib nav containers

This problem may be related to issue: #32 (comment)

After switching from shinyTree to jsTreeR, the callbacks from bslib::navset_pill_list appears to be broken when rendered through renderUI. The desirable behavior is that whenever the user activates a certain nav_panel in the navset_pill_list container, the value the nav_panel should be accessible through the input$id_of_navset_pill_list_container. However, when jsTreeR is used with renderUI, the input$id_of_navset_pill_list_container is never updated when the user activates certain panels, and remains at the initialized value of NULL at all times.

2 Reprex's are made with shinyTree and jsTreeR

Firstly, the reprex with shinyTree showing desirable reactive behaviour:

library(shiny)
library(bslib)
library(shinyWidgets)
library(shinyTree)
library(jsTreeR)

# using shinyTree
nodes_shinyTree<-list(foo=list(bar=list()))

shinyTree_ui<-card(
  shinyTree('shinyTree_tree_ui'),
  verbatimTextOutput(outputId='selected_navset_ui'),
  bslib::navset_pill_list(
    id='navset_ui',
    nav_panel(title="A",value="A"),
    nav_panel(title="B",value="B"))
)

ui_shinyTree<-page_navbar(
  title='shinyTree',
  
  nav_panel(
    title='shinyTree',
    card(
      shinyTree('shinyTree_tree'),
      verbatimTextOutput(outputId='selected_navset'),
      bslib::navset_pill_list(
        id='navset',
        nav_panel(title="A",value="A"),
        nav_panel(title="B",value="B"))
    )
  ),

  nav_panel(
    title='shinyTree uiOutput',
    uiOutput('shinyTree_ui')
  )
  
)

server_shinyTree<-function(input,output,session){
  output$shinyTree_tree<-renderTree(nodes_shinyTree)
  output$selected_navset<-renderText(input$navset)

  output$shinyTree_ui<-renderUI(shinyTree_ui)
  output$shinyTree_tree_ui<-renderTree(nodes_shinyTree)
  output$selected_navset_ui<-renderText({
    if(is.null(input$navset_ui)){
      'select from navset'  
    } else {
      input$navset_ui  
    }
  })
  
}

shinyApp(ui_shinyTree,server_shinyTree)

Now the reprex with jsTreeR, where the input$navset_ui fails to update on change:

library(shiny)
library(bslib)
library(shinyWidgets)
library(shinyTree)
library(jsTreeR)

# using jsTreeR
nodes_jsTreeR<-list(list(text='foo',children=list(list(text='bar'))))

jsTreeR_ui<-card(
    jstreeOutput('jstree_tree_ui'),
    verbatimTextOutput(outputId='selected_navset_ui'),
    bslib::navset_pill_list(
      id='navset_ui',
      nav_panel(title="A",value="A"),
      nav_panel(title="B",value="B"))
)


ui_jsTreeR<-page_navbar(
  title='jsTreeR',
  
  nav_panel(
    title='jsTreeR',
    card(
      jstreeOutput('jstree_tree'),
      verbatimTextOutput(outputId='selected_navset'),
      bslib::navset_pill_list(
        id='navset',
        nav_panel(title="A",value="A"),
        nav_panel(title="B",value="B"))
    )
  ),

  nav_panel(
    title='jsTreeR uiOutput',
    uiOutput('jsTreeR_ui')
  )
)

server_jsTreeR<-function(input,output,session){
  output$jstree_tree<-renderJstree(jstree(nodes_jsTreeR))
  output$selected_navset<-renderText(input$navset)
  
  
  output$jsTreeR_ui<-renderUI(jsTreeR_ui)
  output$jstree_tree_ui<-renderJstree(jstree(nodes_jsTreeR))
  output$selected_navset_ui<-renderText({
    if(is.null(input$navset_ui)){
      'select from navset'  # This is always displayed
    } else {
      input$navset_ui  # This is never displayed since input$navset_ui never updates
    }
  })
  
}

shinyApp(ui_jsTreeR,server_jsTreeR)

The reprex's differ only by which wrapper is used to interface with jsTree (shinyTree vs jsTreeR).

Using remote folders?

Hi, amazing package!

How could we use the 'folder gadget' for a folder in a remote server? i.e.:

folderGadget("http://192.168.0.0/Pictures/", all.files = TRUE)

Best regards.

jsTreeCopied Event not recorded

I need to record events occurring when copying nodes from a tree A to an empty tree B (only a root node) and when deleting them from B.
Everything seems to work fine until I want to add back a previously deleted node from B. There no jsTreeCopied event are detected.

Tree A -> Copy A1 -> Tree B (jsTreeCopied detected)
TreeB -> Delete A1 (jsTreeDeleted detected)
Tree A -> Copy A1 -> Tree B (jsTreeCopied NOT detected)

Exemple :

library(shiny)
library(jsTreeR)

visibleLayersTreeCheckCallback <- JS("function visibleLayersTreeCheckCallback (operation, node, parent, position, more) {
  if(operation === 'move_node' || operation === 'copy_node') {
    if(parent.id === '#' || parent.type === 'child') {
      return false; // prevent moving a child above or below the root
    }               // and moving inside a child
  }
  return true; // allow everything else
}")

sourceLayersTreeCheckCallback <- JS("function sourceLayersTreeCheckCallback (operation, node, parent, position, more) {
  if(operation === 'move_node' || operation === 'copy_node') {
      return false; // prevent moving a child above or below the root            # and moving inside a child
  }
  return true; // allow everything else
}")

visibleLayersTreeMenu<-JS("function visibleLayersTreeMenu(node){
  var tree = $('#visibleLayersTree').jstree(true);
  var items = {
    'rename' : {
      'label' : 'Rename',
      'action' : function (obj) { tree.edit(node); },
      'icon': 'glyphicon glyphicon-edit'
    },
    'delete' : {
      'label' : 'Delete',
      'action' : function (obj) { tree.delete_node(node); },
      'icon' : 'glyphicon glyphicon-trash'
    },
    'create' : {
      'label' : 'Create',
      'action' : function (obj) { tree.create_node(node); },
      'icon': 'glyphicon glyphicon-plus'
    }
  }
  return items;
}")

sourceLayersTreeMenu<-JS("function sourceLayersTreeMenu(node)
{
  var tree = $('#sourceLayersTree').jstree(true);
  var items = {
    'rename' : {
      'label' : 'Rename',
      'action' : function (obj) { tree.edit(node); },
      'icon': 'glyphicon glyphicon-edit'
    },
    'delete' : {
      'label' : 'Delete',
      'action' : function (obj) { tree.delete_node(node); },
      'icon' : 'glyphicon glyphicon-trash'
    },
    'create' : {
      'label' : 'Create',
      'action' : function (obj) { tree.create_node(node); },
      'icon': 'glyphicon glyphicon-plus'
    }
  }
  return items;
}")
dnd_list <- list(
    is_draggable = JS(
        "function(node) {",
        "  return node[0].type === 'child';",
        "}"
    ),
    always_copy = TRUE
)

dnd_visible <- list(
    is_draggable = JS(
        "function(node) {",
        "  return node[0].type === 'child';",
        "}"
    ),
    large_drop_target = TRUE
)

nodes_test <- list(
    list(
        text = "A",
        type = "root",
        children = list(
            list(
            text = "A1",
            type = "child"
            ),
            list(
            text = "A2",
            type = "child"
            )
        )
    ),
    list(
        text = "B",
        type = "root",
        children = list(
            list(
                text = "B1",
                type = "child"
            ),
            list(
                text = "B2",
                type = "child"
            )
        )
    )
)
ui <- shinyUI(
    fluidPage(
        fluidRow(
            column(
                width = 3,
                jstreeOutput("sourceLayersTree"),
            ),
            column(
                width = 3,
                jstreeOutput("visibleLayersTree")
            )
        ),
    )
)

server <- shinyServer(
    function(input, output, session){
        
        output[["sourceLayersTree"]] <- renderJstree({
            jstree(
                nodes_test,
                checkCallback = sourceLayersTreeCheckCallback,
                dragAndDrop = TRUE, dnd = dnd_list,
                selectLeavesOnly = TRUE,
                types = types,
                search = TRUE,
                unique = TRUE,
                sort = TRUE,
                wholerow = TRUE,
                contextMenu = list(items = sourceLayersTreeMenu)
            )
        })
        
        output[["visibleLayersTree"]] <- renderJstree({
            jstree(
                nodes = list(list(text = "Visible Layers", type = "root", state = list(opened = TRUE))),
                checkCallback = visibleLayersTreeCheckCallback,
                dragAndDrop = TRUE,dnd = dnd_visible,
                selectLeavesOnly = TRUE,
                types = types,
                search = TRUE,
                unique = TRUE,
                wholerow = TRUE,
                contextMenu = list(items = visibleLayersTreeMenu)
            )
        })
        observeEvent(input[["jsTreeCopied"]],
            {
                node_path <- input[["jsTreeCopied"]][["from"]][["path"]]
                cat("Copied\n")
            })

        observeEvent(input[["jsTreeDeleted"]],
            {
            cat("Deleted\n")
            })
    }
)

shinyApp(ui, server)

Expected output :
Copied Deleted Copied
Output :
Copied Deleted
Is there a way to make jsTreeCopied consistent ?

Best regards,
Thanks

[feature request] updateTree

Could we get a new function updateTree? I'm using {jsTreeR} in an application with over a 1,000 leaf nodes and destroying the tree and re-rendering it gives severe flickering and sometimes 0.5 seconds of a white screen. Competing packages do the same: {shinyTree} has updateTree and {shinyWidgets} has updateTreeInput for better performance.

Use in shiny package development

Can the usage be extended for instances in which a user does not want to attach {jsTreeR}? For instance, I do work on developing Shiny applications that are distributed as packages. This results in no formal attaching of {jsTreeR} (e.g. library(jsTreeR)), but instead will load {jsTreeR} functions in the namespace. Since the package is never attached, the JS handlers are never created.

Would you consider switching from .onAttach() to .onLoad(), or including a helper function (e.g. use_jsTree()) that can be called in the server function of a Shiny app?

jstreeOutput is not re-rendered in shiny

When creating a jstree with reactive dependency regarding its nodes it isn't re-rendered in shiny.

We can see that the new object is generated (message: Populating tree using a list.) and we can print it after a browser() call, however the shiny UI doesn't change.

Expected output when typing "Test":

image

Please see the following example:

library(jsTreeR)
library(shiny)

ui <- fluidPage(
  textInput("test", "test"),
  jstreeOutput("mytree")
)

server <- function(input, output, session){
  output[["mytree"]] <- renderJstree({
    nodes <- list(
      list(
        text = paste0(input$test, "Branch 1"),
        state = list(opened = TRUE, disabled = FALSE),
        type = "parent",
        children = list(
          list(text = "Leaf A", type = "child"),
          list(text = "Leaf B", type = "child"),
          list(text = "Leaf C", type = "child"),
          list(text = "Leaf D", type = "child")
        )
      ),
      list(text = "Branch 2", type = "parent", state = list(opened = TRUE))
    )
    # browser()
    jstree(nodes)
  })
}  

shinyApp(ui, server)

nok

PS: I've seen jstreeUpdate - however, I think completely re-rendering the widget should also be possible.

Selected input loses hierarchy

I've got a fairly large menu structure (almost 10k menu items) and there is a lot of repetition in the leaf nodes - the parent level provides critical context which is needed to identify what data the leaf node relates to. From what I can see, just using str() to display the selected values, the results are returned as a one-dimensional list and the hierarchy is lost. Is there a way around this?

I have started using jsTreeR after struggling with the limitations of shinyTree, but shinyTree did maintain the structure of the menu data, so I'm thinking this must be possible.

Here is an example:

library(shiny)
library(jsTreeR)

shinyApp(
    ui = fluidPage(
        titlePanel("Multi-level menu example"),
        fluidRow(
            column(6, jstreeOutput("menu")),
            column(6, verbatimTextOutput("str"))
        )
    ),
    server = function(input, output) {
        output$menu <- renderJstree({
            jstree(
                list(
                    list(
                        text = "Product A",
                        children = list(
                            list(
                                text = "Version 3",
                                children = list(
                                    list(text = "A"),
                                    list(text = "B"),
                                    list(text = "C")
                                )
                            ),
                            list(
                                text = "Version 2",
                                children = list(
                                    list(text = "A"),
                                    list(text = "B"),
                                    list(text = "C")
                                )
                            ),
                            list(
                                text = "Version 1",
                                children = list(
                                    list(text = "A"),
                                    list(text = "B")
                                )
                            )
                        )
                    ),
                    list(
                        text = "Product B",
                        children = list(
                            list(
                                text = "Version 1",
                                children = list(
                                    list(text = "A")
                                )
                            )
                        )
                    )
                )
            , checkboxes = TRUE)
        })
        output$str <- renderPrint({
            req(input$menu_selected)
            str(input$menu_selected)
        })
    }
)

Misaligned grid if search is activated

When setting search = TRUE for a jstree() using a grid, the rows are no longer aligned (please find the example code below):

image

I'm wondering if the (overall) search box can be placed above the table or column-wise search boxes as shown here can be realized instead (Maybe with an option to place the search boxes on top, but below the column titles, just like datatables column search) using the searchColumn function provided by the grid plugin:

image

library(jsTreeR)
library(shiny)

nodes <- list(
  list(
    text = "Fruits",
    type = "fruit",
    icon = "supertinyicon-transparent-raspberry_pi",
    a_attr = list(class = "helvetica"),
    children = list(
      list(
        text = "Apple",
        type = "fruit",
        data = list(
          price = 0.1,
          quantity = 20,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Banana",
        type = "fruit",
        data = list(
          price = 0.2,
          quantity = 31,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Grapes",
        type = "fruit",
        data = list(
          price = 1.99,
          quantity = 34,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Mango",
        type = "fruit",
        data = list(
          price = 0.5,
          quantity = 8,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Melon",
        type = "fruit",
        data = list(
          price = 0.8,
          quantity = 4,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Pear",
        type = "fruit",
        data = list(
          price = 0.1,
          quantity = 30,
          cssclass = "lightorange"
        )
      ),
      list(
        text = "Strawberry",
        type = "fruit",
        data = list(
          price = 0.15,
          quantity = 32,
          cssclass = "lightorange"
        )
      )
    ),
    state = list(
      opened = TRUE
    )
  ),
  list(
    text = "Vegetables",
    type = "vegetable",
    icon = "supertinyicon-transparent-vegetarian",
    a_attr = list(class = "helvetica"),
    children = list(
      list(
        text = "Aubergine",
        type = "vegetable",
        data = list(
          price = 0.5,
          quantity = 8,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Broccoli",
        type = "vegetable",
        data = list(
          price = 0.4,
          quantity = 22,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Carrot",
        type = "vegetable",
        data = list(
          price = 0.1,
          quantity = 32,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Cauliflower",
        type = "vegetable",
        data = list(
          price = 0.45,
          quantity = 18,
          cssclass = "lightgreen"
        )
      ),
      list(
        text = "Potato",
        type = "vegetable",
        data = list(
          price = 0.2,
          quantity = 38,
          cssclass = "lightgreen"
        )
      )
    )
  )
)

grid <- list(
  columns = list(
    list(
      width = 200,
      header = "Product",
      headerClass = "bolditalic yellow centered",
      wideValueClass = "cssclass"
    ),
    list(
      width = 150,
      value = "price",
      header = "Price",
      wideValueClass = "cssclass",
      headerClass = "bolditalic yellow centered",
      wideCellClass = "centered"
    ),
    list(
      width = 150,
      value = "quantity",
      header = "Quantity",
      wideValueClass = "cssclass",
      headerClass = "bolditalic yellow centered",
      wideCellClass = "centered"
    )
  ),
  width = 600
)

types <- list(
  fruit = list(
    a_attr = list(
      class = "lightorange"
    ),
    icon = "supertinyicon-transparent-symantec"
  ),
  vegetable = list(
    a_attr = list(
      class = "lightgreen"
    ),
    icon = "supertinyicon-transparent-symantec"
  )
)

ui <-   fluidPage(
  tags$head(
    tags$style(
      HTML(c(
        ".lightorange {background-color: #fed8b1;}",
        ".lightgreen {background-color: #98ff98;}",
        ".bolditalic {font-weight: bold; font-style: italic; font-size: large;}",
        ".yellow {background-color: yellow !important;}",
        ".centered {text-align: center; font-family: cursive;}",
        ".helvetica {font-weight: 700; font-family: Helvetica; font-size: larger;}"
      ))
    )
  ),
  titlePanel("jsTree grid"),
  jstreeOutput("jstree")
)

server <-   function(input, output){
  output[["jstree"]] <-
    renderJstree(jstree(nodes, search = TRUE, grid = grid, types = types))
}

shinyApp(ui, server)

Pass undetermined state to input$treeID_full

Currently I cannot extract the third state of a checkbox:

image

In this state (some childs selected, others not) selected or checked are FALSE by default.

As far as I understand this state is called undetermined, as shown here:
image

I tried to access it, but it doesn't seem to change:

library(jsTreeR)
library(shiny)
library(jsonlite)

nodes <- list(
  list(
    text = "Branch 1",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE,
      undetermined = TRUE
    ),
    type = "parent",
    children = list(
      list(
        text = "Leaf A",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE,
          undetermined = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf B",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = FALSE,
          undetermined = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf C",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = TRUE,
          undetermined = FALSE
        ),
        type = "child"
      ),
      list(
        text = "Leaf D",
        state = list(
          opened = TRUE,
          disabled = FALSE,
          selected = FALSE,
          checked = TRUE,
          undetermined = FALSE
        ),
        type = "child"
      )
    )
  ),
  list(
    text = "Branch 2",
    type = "parent",
    state = list(
      opened = TRUE,
      disabled = FALSE,
      selected = FALSE,
      checked = TRUE,
      undetermined = FALSE
    )
  )
)

ui <- fluidPage(jstreeOutput("mytree"),  verbatimTextOutput("mytree_full"))

server <- function(input, output, session) {
  output[["mytree"]] <- renderJstree({
    jstree(nodes, contextMenu = TRUE, checkboxes = TRUE, checkWithText = FALSE)
  })
  
  output$mytree_full <-  renderPrint({toJSON(input$mytree_full, pretty = TRUE)})
}



shinyApp(ui, server)

jstree update in shiny

I want to display a tree which updates each time I change the path.
Classic reactive code does not seem to work properly : the tree appears the first time I enter a path, but when I change the path, tree stay frozen.
I may miss something here
tree.zip
.

jstreeDestroy updates in shiny

I've run into an issue where I am feeding a reactive node list into the renderjstree function inside of a module, and the jstree does not update properly when the reactive updates. To get around this, a previous issue recommended using the jstreeDestroy function before updating the jstree data structure. I was able to update the jstree by doing this, but the search bar does not get destroyed.

3 things stood out to me while investigating this:

  1. The search bar is not properly destroyed by the jstreeDestroy function. (issue)
  2. Shouldn't jstreeDestroy be automatically called on any re-renders? (feature request)
  3. Note that for modular shiny functions, the jstreeDestroy function does not properly handle the different namespaces. (issue or feature request)

This is my minimal example below:

library(shiny)
library(jsTreeR)

#Module with jstree
jstreeUI <- function(id) {
  ns <- NS(id)
  jstreeOutput(ns('tree'))
}


jstreeServer <- function(input,output,session,nodes=reactive(NULL)) {
  output$tree <- renderJstree({
    #Destroy the tree each time
    jstreeDestroy(session, session$ns('tree'))

    #Call the tree
    jsTreeR::jstree(
      nodes = nodes(),
      search = TRUE,
      multiple = FALSE,
      theme = "proton")
  })
}


#Main ui and server functions
mainUI <- fluidPage(
  checkboxInput('list1','A',value=T),
  jstreeUI('a')
)


mainServer <- function(input,output, session) {
  #Update the data structure when the checkbox changes
  nodes <- reactive({
    if (input$list1) {
      return(list(list(text = "AAAAAAA")))
    } else {
      return(list(list(text = "BBBBBBB")))
    }
  })

  #Call module with jstree
  callModule(module=jstreeServer, id='a', nodes=nodes)
}

#Run shiny app
shinyApp(ui = mainUI, server = mainServer)

Cannot select node with child

I want to be able to select just one node that may or may not have children and only return that node.

Two issues:

  1. With "multiple" set to FALSE, multiple children will be returned when selecting a node.
  2. When I want to select a node but not its children, all of the children are selected and returned rather than the node.

Is there a way to determine the actual node that was selected when the selected node has children?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.