JsTree Example
On this page
Stats
#callbacks:
#callbacks:
Countries
AnonSelect handler can also be set.
lazy val data = Source.fromResource("world-cities.csv").getLines().drop(1).map(_.split(",")).collect({
case Array(name, country, subcountry, geonameid) => (country, subcountry, name)
}).toVector
lazy val country2Region2City: Map[String, Map[String, Vector[String]]] = data.groupBy(_._1).transform((k, v) => v.groupBy(_._2).transform((k, v) => v.map(_._3)))
val jsTree = new JSTree[JSTreeSimpleNode[Unit]] {
override val rootNodes: Seq[JSTreeSimpleNode[Unit]] =
List(new JSTreeSimpleNode[Unit]("Cities of the world", (), s"root")(
country2Region2City.toVector.sortBy(_._1).map({
case (country, region2City) =>
new JSTreeSimpleNode[Unit](country, (), s"c_$country")(
region2City.toVector.sortBy(_._1).map({
case (region, cities) =>
new JSTreeSimpleNode[Unit](region, (), s"r_$region")(
cities.sorted.map(city =>
new JSTreeSimpleNode[Unit](city, (), s"c_$city", true)(Nil)
)
)
})
)
})
))
}
val onSelect = fsc.callback(
Js("data.node.id"),
nodeId => {
println(s"JsTree Selected Node is: $nodeId")
JS.consoleLog(s"JsTree Selected Node is: $nodeId")
},
)
jsTree.render().withStyle("height: 600px; overflow: auto;")
++ jsTree.init(onSelect = onSelect).onDOMContentLoaded.inScriptTagCountries (dedicated classes)
Same functionality but with dedicated classes for RootNode, CountryNode, RegionNode and CityNode.
import com.fastscala.components.bootstrap5.helpers.BSHelpers.*
lazy val data = Source.fromResource("world-cities.csv").getLines().drop(1).map(_.split(",")).collect({
case Array(name, country, subcountry, geonameid) => (country, subcountry, name)
}).toVector
abstract class Node(title: String) extends JSTreeNode[Node] {
override def titleNs: NodeSeq = span.apply(title)
val open: Boolean = false
val disabled: Boolean = false
val icon: Option[String] = None
}
class RootNode(country2Region2City: Map[String, Map[String, Vector[String]]]) extends Node("Cities of the world") {
val id: String = "root"
override def childrenF: () => collection.Seq[Node] = () => country2Region2City.toVector.sortBy(_._1).map({
case (country, region2City) => new CountryNode(country, region2City)
})
}
class CountryNode(countryName: String, region2City: Map[String, Vector[String]]) extends Node(countryName) {
val id: String = "country-" + countryName
override def childrenF: () => collection.Seq[Node] = () => region2City.toVector.sortBy(_._1).map({
case (region, cities) => new RegionNode(region, cities)
})
}
class RegionNode(regionName: String, cities: Vector[String]) extends Node(regionName) {
val id: String = "region-" + regionName
override def childrenF: () => collection.Seq[Node] = () => cities.sorted.map({
case city => new CityNode(city)
})
}
class CityNode(cityName: String) extends Node(cityName) {
val id: String = "city-" + cityName
override def childrenF: () => collection.Seq[Node] = () => Nil
}
val jsTree = new JSTree[Node] {
override def rootNodes: Seq[Node] = List(new RootNode(data.groupBy(_._1).transform((k, v) => v.groupBy(_._2).transform((k, v) => v.map(_._3)))))
}
val onSelect = fsc.callback(
Js("data.node.id"),
nodeId => {
println(s"JsTree Selected Node is: $nodeId")
JS.consoleLog(s"JsTree Selected Node is: $nodeId")
},
)
jsTree.render().withStyle("height: 600px; overflow: auto;")
++ jsTree.init(onSelect = onSelect).onDOMContentLoaded.inScriptTagWith menu
import com.fastscala.components.bootstrap5.helpers.BSHelpers.*
class Node(
val titleNs: NodeSeq,
val value: Unit,
val id: String,
val open: Boolean = false,
val disabled: Boolean = false,
val icon: Option[String] = None,
) extends JSTreeNodeWithContextMenu[Node] {
override def childrenF: () => Seq[Node] = () => Nil
override def actions: Seq[JSTreeContextMenuAction] = Seq(
new DefaultJSTreeContextMenuAction(
label = "Open",
icon = Some("bi bi-book text-success"),
action = Some(implicit fsc => JS.alert(s"Open from node: $id")),
),
new DefaultJSTreeContextMenuAction(
label = "Close",
icon = Some("bi bi-book text-info"),
action = Some(implicit fsc => JS.alert(s"Close from node: $id")),
),
new DefaultJSTreeContextMenuAction(
label = "SubMenu",
icon = Some("bi bi-book text-warning"),
separatorAfter = false,
action = None,
subactions = Seq(
new DefaultJSTreeContextMenuAction(
label = "SubOpen",
icon = Some("bi bi-book text-success"),
action = Some(implicit fsc => JS.alert(s"Open in SubMenu from node: $id")),
),
new DefaultJSTreeContextMenuAction(
label = "SubClose",
icon = Some("bi bi-book text-info"),
separatorAfter = false,
action = Some(implicit fsc => JS.alert(s"Close in SubMenu from node: $id")),
),
),
),
)
}
implicit val jsTree: JSTreeWithContextMenu[Node] = new JSTreeWithContextMenu[Node] {
override val rootNodes = Seq(new Node(
span("root"),
(),
"root"
))
}
jsTree.render() ++ jsTree.init().onDOMContentLoaded.inScriptTagEditable
NOTE: work in progress
import com.fastscala.components.bootstrap5.helpers.BSHelpers.*
class Node(
val id: String,
var title: String,
val open: Boolean = false,
val disabled: Boolean = false,
val icon: Option[String] = None,
)(implicit jsTree: JSTreeWithContextMenu[Node]) extends EditableJSTreeNode[Node]()(jsTree) {
override val allowDuplicated: Boolean = false
override val children: ArrayBuffer[Node] = ArrayBuffer[Node]()
override def actions: Seq[JSTreeContextMenuAction] = Seq(
new DefaultCreateAction(
label = "New",
icon = Some("bi bi-book text-danger"),
// business logic and db operations can be here, e.g. insert a new record into database
onCreate = newId => new Node(newId, newId),
// business logic and db operations can be here, e.g. update a title field in database
onEdit = (node, newTitle) => { node.title = newTitle; Js.Void}
),
new DefaultRenameAction(
label = "Rename",
icon = Some("bi bi-book text-danger"),
// business logic and db operations can be here, e.g. update a title field in database
onEdit = (node, newTitle) => { node.title = newTitle; Js.Void}
),
new DefaultRemoveAction(
label = "Remove",
icon = Some("bi bi-book text-danger"),
// business logic and db operations can be here, e.g. delete a new record in database
// But Note: the node has already been removed from its parent's children, Don't do it again!
onRemove = (node, pid) => JS.alert(s"node(${node.id}) removed from $pid")
),
)
}
val editableJSTree: JSTreeWithContextMenu[Node] = new JSTreeWithContextMenu[Node] {
override val rootNodes = Seq(new Node("root", "Example")(this))
}
editableJSTree.render() ++ editableJSTree.init().onDOMContentLoaded.inScriptTagDrag and Drop Support
import com.fastscala.components.bootstrap5.helpers.BSHelpers.*
class Node(
var title: String,
val id: String = UUID.randomUUID().toString,
val open: Boolean = false,
val disabled: Boolean = false,
val icon: Option[String] = None,
)(
val children: ArrayBuffer[Node] = ArrayBuffer[Node]()
) extends JSTreeNodeWithDragAndDrop[Node] {
override def titleNs: NodeSeq = <span>{title}</span>
override def childrenF: () => collection.Seq[Node] = () => children
override def addedChildNode(child: Node, fromIdx: Int): Js = Js.Void
override def removedChildNode(child: Node, toIdx: Int): Js = Js.Void
}
val editableJSTree: JSTreeWithDragAndDrop[Node] = new JSTreeWithDragAndDrop[Node] {
override def movedNode(node: Node, fromParent: Node, fromIdx: Int, toParent: Node, toIdx: Int): Js = Js.Void
override val rootNodes = Seq(new Node("Root node")(
ArrayBuffer(
new Node("Folder A")(
ArrayBuffer(
new Node("Item A.1")(),
new Node("Item A.2")(),
new Node("Item A.3")(),
),
),
new Node("Folder B")(
ArrayBuffer(
new Node("Item B.1")(),
new Node("Item B.2")(),
new Node("Item B.3")(),
),
),
),
))
}
editableJSTree.render() ++ editableJSTree.init().onDOMContentLoaded.inScriptTag