JS Tree - Select parent node when all the child nodes are selected - jstree

I am using JSTree. When you check a node, I want to see if its all sibling nodes are also selected, if yes, I want to select the parent node and deselect all the child nodes. How can I achieve this with JsTree?

You may need to tweak it to fit your situation but this should be basically what you need:
$(jsTreeSelector).on("select_node.jstree", function (node, selected) {
var parentNode = $(jsTreeSelector).jstree(true).get_parent(selected.node.id);
var siblingNodes = $(jsTreeSelector).jstree(true).get_children_dom(parentNode);
var allChecked = true;
$(siblingNodes).each(function () {
if (!$(this).children('.jstree-anchor').hasClass('jstree-clicked')) allChecked = false;
});
if (allChecked) {
$(siblingNodes).each(function () {
$(jsTreeSelector).jstree(true).deselect_node(this);
});
$(jsTreeSelector).jstree(true).select_node(parentNode);
}
});
Make sure three_state is set to false in your tree config

Related

disable checkbox in angular tree component

I am unable to find any way to disable checkbox node using angular tree component.
There is no option to mark the tree disabled so that the checkboxes that appear alongwith th etree data should be disabled. Please suggest
Disabling the node can be done by actionMapping inside options attribute https://angular2-tree.readme.io/v1.2.0/docs/options. Here click event of mouse can be overwritten.
<Tree [nodes]="nodes" [options]="treeOptions"></Tree>
In my tree data I kept an attribute isSelectable on each node, which is true|false. In case of true i proceed selecting the node otherwise it does not do anything. Here are full options that I am passing to the tree component.
public options: ITreeOptions = {
isExpandedField: 'expanded',
idField: 'uuid',
getChildren: this.getChildren.bind(this),
actionMapping: {
mouse: {
click: (tree, node, $event) => {
if ( node.data.isSelectable ) {
this.isNodeSelected.emit(node.data);
this.alreadySelected = true;
this.preSelected.tree = tree;
this.preSelected.node = node;
this.preSelected.event = $event;
TREE_ACTIONS.ACTIVATE(this.preSelected.tree, this.preSelected.node, this.preSelected.event);
}
}
}
},
nodeHeight: 23,
allowDrag: (node) => {
return false;
},
allowDrop: (node) => {
return false;
}
};

jsTree get siblings of a node

I'm using drag and drop with jsTree, and when I drag a node to a new position, I have to update not only its position, but the positions of its siblings too in the database.
Here's an example:
If I drag New node into node Gamme 1, now it's siblings are gamme 501 and gamme 500
Getting data about the moved node and updating it's position in db is not a problem:
.on("move_node.jstree", function (e, data) {
console.log(data);
$.ajax({
type: "POST",
url: Routing.generate('saveGammeNewPosition'),
data: {
gammeId: data.node.id,
newParentId: data.node.parent,
position: data.position
}
});
})
But I have no idea how to get information about the new siblings of a node that has been moved.
When I do something like this, unfortunately I get no data on position as the json jsTree accepts has no position attribute:
$('#tree').jstree(true).get_json(data.node.parent, {flat: true})
So is there a way I can get data on positions of the siblings?
You can get the new parent of the node in the move_node event and use that to get its immediate children.
function getSiblings(nodeID, parent) {
var tree = $('#tree').jstree(true),
parentNode = tree.get_node(parent),
aChildren = parentNode.children,
aSiblings = [];
aChildren.forEach(function(c){
if(c !== nodeID) aSiblings.push(c);
});
return aSiblings;
}
$('#tree').on("move_node.jstree", function (e, data) {
var aSiblings = getSiblings(data.node.id, data.parent);
console.log(aSiblings);
});

Leaflet Trigger Event on Clustered Marker by external element

I just starting to learn about Leaflet.js for my upcoming project.
What i am trying to accomplish:
I need to make a list of marker which displayed on the map, and when the list item is being hovered (or mouseover) it will show where the position on the map (for single marker, it should change its color. For Clustered marker, it should display Coverage Line like how it behave when we hover it.. and perhaps change its color too if possible).
The map should not be changed as well as the zoom level, to put it simply, i need to highlight the marker/ Cluster on the map.
What i have accomplished now : I am able to do it on Single Marker.
what i super frustrated about : I failed to find a way to make it happen on Clustered Marker.
I use global var object to store any created marker.
function updateMapMarkerResult(data) {
markers.clearLayers();
for (var i = 0; i < data.length; i++) {
var a = data[i];
var myIcon = L.divIcon({
className: 'prop-div-icon',
html: a.Description
});
var marker = L.marker(new L.LatLng(a.Latitude, a.Longitude), {
icon: myIcon
}, {
title: a.Name
});
marker.bindPopup('<div><div class="row"><h5>Name : ' + a.Name + '</h5></div><div class="row">Lat : ' + a.Latitude + '</div><div class="row">Lng : ' + a.Longitude + '</div>' + '</div>');
marker.on('mouseover', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon");
this._icon.classList.add("prop-div-icon-shadow");
}
});
marker.on('mouseout', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon-shadow");
this._icon.classList.add("prop-div-icon");
}
});
markersRef[a.LocId] = marker; // <-- Store Reference
markers.addLayer(marker);
updateMapListResult(a, i + 1);
}
map.addLayer(markers);
}
But i don't know which object or property to get the Clustered Marker reference.
And i trigger the marker event by my global variable (which only works on single marker).
...
li.addEventListener("mouseover", function(e) {
jQuery(this).addClass("btn-info");
markersRef[this.getAttribute('marker')].fire('mouseover'); // --> Trigger Marker Event "mouseover"
// TODO : Trigger ClusteredMarker Event "mouseover"
});
...
This is my current https://jsfiddle.net/oryza_anggara/2gze75L6/, any lead could be a very big help. Thank you.
Note: the only js lib i'm familiar is JQuery, i have no knowledge for others such as Angular.js
You are probably looking for markers.getVisibleParent(marker) method, to retrieve the containing cluster in case your marker is clustered.
Unfortunately, it is then not enough to fire your event on that cluster. The coverage display functionality is set on the Cluster Group, not on its individual clusters. Therefore you need to fire your event on that group:
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// In case the marker is hidden in a cluster, have the clusterGroup
// show the regular coverage polygon.
markers.fire(eventName, {
layer: visibleLayer
});
} else {
marker.fire(eventName);
}
}
var marker = markersRef[this.getAttribute('marker')];
_fireEventOnMarkerOrVisibleParentCluster(marker, 'mouseover');
Updated JSFiddle: https://jsfiddle.net/2gze75L6/5/
That being said, I think another interesting UI, instead of showing the regular coverage polygon that you get when "manually" hovering a cluster, would be to spiderfy the cluster and highlight your marker. Not very easy to implement, but the result seems nice to me. Here is a quick try, it would probably need more work to make it bullet proof:
Demo: https://jsfiddle.net/2gze75L6/6/
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
if (eventName === 'mouseover') {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// We want to show a marker that is currently hidden in a cluster.
// Make sure it will get highlighted once revealed.
markers.once('spiderfied', function() {
marker.fire(eventName);
});
// Now spiderfy its containing cluster to reveal it.
// This will automatically unspiderfy other clusters.
visibleLayer.spiderfy();
} else {
// The marker is already visible, unspiderfy other clusters if
// they do not contain the marker.
_unspiderfyPreviousClusterIfNotParentOf(marker);
marker.fire(eventName);
}
} else {
// For mouseout, marker should be unclustered already, unless
// the next mouseover happened before?
marker.fire(eventName);
}
}
function _unspiderfyPreviousClusterIfNotParentOf(marker) {
// Check if there is a currently spiderfied cluster.
// If so and it does not contain the marker, unspiderfy it.
var spiderfiedCluster = markers._spiderfied;
if (
spiderfiedCluster
&& !_clusterContainsMarker(spiderfiedCluster, marker)
) {
spiderfiedCluster.unspiderfy();
}
}
function _clusterContainsMarker(cluster, marker) {
var currentLayer = marker;
while (currentLayer && currentLayer !== cluster) {
currentLayer = currentLayer.__parent;
}
// Say if we found a cluster or nothing.
return !!currentLayer;
}

How to get selected layers in control.layers?

Is there a way to select all selected layers in the control.layers with leaflet api?
I can do it with the help of jquery like this :
$('.leaflet-control-layers-selector:checked')
But maybe there is an api?
Thanks
There is no API for that but you could easily create one yourself:
// Add method to layer control class
L.Control.Layers.include({
getActiveOverlays: function () {
// Create array for holding active layers
var active = [];
// Iterate all layers in control
this._layers.forEach(function (obj) {
// Check if it's an overlay and added to the map
if (obj.overlay && this._map.hasLayer(obj.layer)) {
// Push layer to active array
active.push(obj.layer);
}
});
// Return array
return active;
}
});
var control = new L.Control.Layers(...),
active = control.getActiveOverlays();
Based on iH8's answer
L.Control.Layers.include({
getOverlays: function() {
// create hash to hold all layers
var control, layers;
layers = {};
control = this;
// loop thru all layers in control
control._layers.forEach(function(obj) {
var layerName;
// check if layer is an overlay
if (obj.overlay) {
// get name of overlay
layerName = obj.name;
// store whether it's present on the map or not
return layers[layerName] = control._map.hasLayer(obj.layer);
}
});
return layers;
}
});
Now you can use
var control = new L.Control.Layers(...)
control.getOverlays(); // { Truck 1: true, Truck 2: false, Truck 3: false }
I find this a little more useful because
all the layers are included
the key is the name of the layer
if the layer is showing, it has a value of of true, else false

jsTree Search: show subnodes

How can I make jsTree hide non-matching elements, but still display the subnodes of matched elements?
As far as I know there are only two possibilities, that are both not suited for this case. You can either make jsTree hide no element at all by setting
show_only_matches: false
Or you can make it hide all non-matched elements by setting
show_only_matches: true
But this option also hides the subnodes of matched nodes.
Finally, I found the solution, though it doesn't look really nice, but it works.
Just pass a the found elements the the enableSubtree()-function, and it will show the nodes and take care of the correct appearance (i.e. that the dotted lines are shown and hidden correctly).
enableSubtree = function(elem) {
elem.siblings("ul:first").find("li").show();
return correctNode(elem.siblings("ul:first"));
};
correctNode = function(elem) {
var child, children, last, _j, _len1, _results;
last = elem.children("li").eq(-1);
last.addClass("jstree-last");
children = elem.children("li");
console.log(children);
_results = [];
for (_j = 0, _len1 = children.length; _j < _len1; _j++) {
child = children[_j];
_results.push(correctNode($(child).children("ul:first")));
}
return _results;
};
A call of this function could look like this:
enableSubtree($(".jstree-search"))
since all found nodes receive the CSS class .jstree-search.
"search" : {
"show_only_matches" : true,
"show_only_matches_children" : true
}
Did you try to overload "search.jstree" with something like it ? (with show_only_matches==false)
$("#mytreecontainerid").bind("search.jstree", function (e, data) {
// close the whole tree if a search text in set
if (data.rslt.str.length>0) $("#mytreecontainerid").jstree('close_all');
// open the found nodes' parent
for (var i = 0 ; i<data.rslt.nodes.length ; i++) {
data.inst._get_parent(data.rslt.nodes[i]).open_node(this, false);
/* here you can do any additional effect to your node */
}
});
Here is a simple code I did for the filtering.
filterTree = function (elem) {
$('li a', elem).not(".jstree-search").parent().css('display', 'none');
$('a.jstree-search', elem).parentsUntil(elem, 'li').css('display', 'block');
};
and call it with the jstree instance..
filterTree($treeVar);
It might not be an optimal solution, but it works perfectly :)