I'm wondering if it is possible to create a subset of uneditable leaf nodes with {jsTreeR}.
The expected behaviour for the leafs is, that they can't be:
Moreover, branch nodes should not be deleted if they have leafs. However the leafs can be dragged and dropped among branch or parent nodes.
I've found the option state = list(disabled = TRUE)
but this doesn't seem to affect the context menu:
library(jsTreeR)
library(shiny)
nodes <- list(
list(
text = "Branch 1",
state = list(opened = TRUE, disabled = FALSE),
type = "parent",
children = list(
list(text = "Leaf A", type = "child", state = list(disabled = TRUE), data = list(customdata = 1)),
list(text = "Leaf B", type = "child", state = list(disabled = FALSE), data = list(customdata = 2)),
list(text = "Leaf C", type = "child", state = list(disabled = FALSE), data = list(customdata = 3)),
list(text = "Leaf D", type = "child", state = list(disabled = FALSE), data = list(customdata = 4))
)
),
list(text = "Branch 2", type = "parent", state = list(opened = TRUE))
)
ui <- fluidPage(
jstreeOutput("jstree")
)
server <- function(input, output, session){
output[["jstree"]] <- renderJstree({
suppressMessages(jstree(
nodes,
search = list(
show_only_matches = TRUE,
case_sensitive = FALSE,
search_leaves_only = FALSE
),
dragAndDrop = TRUE,
multiple = TRUE,
contextMenu = TRUE,
types = list(default = list(icon = "fa fa-caret-right"), child = list(icon = "fa-solid fa-leaf"), parent = list(icon = "fa-brands fa-pagelines")),
theme = "proton"
))
})
}
shinyApp(ui, server)
Any hint is appreciated.
Edit: @StéphaneLaurent's approach wrapped in a shiny app:
library(jsTreeR)
library(shiny)
nodes <- list(
list(
text = "Branch 1",
state = list(opened = TRUE, disabled = FALSE),
type = "parent",
children = list(
list(text = "Leaf A", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 1)),
list(text = "Leaf B", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 2)),
list(text = "Leaf C", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 3)),
list(text = "Leaf D", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 4))
)
),
list(text = "Branch 2", type = "parent", state = list(opened = TRUE))
)
ui <- fluidPage(
jstreeOutput("mytree")
)
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) { if(node.type !== 'undeletable') tree.delete_node(node); },",
" 'icon' : 'glyphicon glyphicon-trash'",
" }",
" }",
" return items;",
"}")
server <- function(input, output, session){
output[["mytree"]] <- renderJstree({
suppressMessages(jstree(
nodes,
search = list(
show_only_matches = TRUE,
case_sensitive = FALSE,
search_leaves_only = FALSE
),
dragAndDrop = TRUE,
multiple = TRUE,
contextMenu = list(items = customMenu),
types = list(default = list(icon = "fa fa-caret-right"), undeletable = list(icon = "fa-solid fa-leaf"), parent = list(icon = "fa-brands fa-pagelines")),
theme = "proton"
))
})
}
shinyApp(ui, server)
Here is another approach (based on the answer given here), which disables the context menu items instead of doing nothing after they were clicked.
The advantage is, that the items are greyed out.
The following also takes into account the problem of not allowing the deletion of branches having leaf nodes:
library(jsTreeR)
library(shiny)
nodes <- list(
list(
text = "Branch 1",
state = list(opened = TRUE, disabled = FALSE),
type = "parent",
children = list(
list(text = "Leaf A", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 1)),
list(text = "Leaf B", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 2)),
list(text = "Leaf C", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 3)),
list(text = "Leaf D", type = "undeletable", state = list(disabled = FALSE), data = list(customdata = 4))
)
),
list(text = "Branch 2", type = "parent", state = list(opened = TRUE))
)
ui <- fluidPage(
jstreeOutput("mytree")
)
customMenu <- 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'
}
}
if (node.children.length > 0) { items.delete._disabled = true; }
if (node.type === 'undeletable') { items.rename._disabled = true;
items.delete._disabled = true;
items.create._disabled = true;
}
return items;
}")
server <- function(input, output, session){
output[["mytree"]] <- renderJstree({
suppressMessages(jstree(
nodes,
search = list(
show_only_matches = TRUE,
case_sensitive = FALSE,
search_leaves_only = FALSE
),
dragAndDrop = TRUE,
multiple = TRUE,
contextMenu = list(items = customMenu),
types = list(default = list(icon = "fa fa-caret-right"), undeletable = list(icon = "fa-solid fa-leaf"), parent = list(icon = "fa-brands fa-pagelines")),
theme = "proton"
))
})
}
shinyApp(ui, server)