jsTree get siblings of a node - jstree

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);
});

Related

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;
}

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

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

How do I split constructed jstree into 2 parts

I am using jstree and jstree grid for contructing my tree structure. Can I split the constructed jstree into 2 parts?
You can split it with code as below.
Check demo: Fiddle
$("#tree1")
.on("select_node.jstree", function(e, data) {
// destroy second tree and remove DOM elements
if ($("#tree2").find('ul').length > 0) {
$("#tree2").jstree('destroy').empty();
}
// get json format for branch to move selected node/branch from first tree
var json = data.instance.get_json(data.node);
// init second tree with new data
$("#tree2").jstree({
"core": {
"data": json
}
});
// remove selected node/branch from the first tree
$("#tree1").jstree('delete_node', [data.node.id]);
$("#" + data.node.id).remove();
})
.jstree({
"core": {
"data": data1
}
})

Adding CartoDb layer to Leaflet Layer Control

I'm trying to toggle the display of a CartoDb layer on a Leaflet map. I've been able to load the layer using this code:
var layerUrl = 'http://ronh-aagis.cartodb.com/api/v1/viz/rotaryclubs_geo2/viz.json';
var clubPts = cartodb.createLayer(map, layerUrl, {
// The ST_AsGeoJSON(ST_Simplify(the_geom,.01)) as geometry will store a simplified GeoJSON representation of each polygon as an attribute we can pick up on hover
query: 'select *, ST_AsGeoJSON(the_geom) as geometry from {{table_name}}',
interactivity: 'cartodb_id, geometry'
})
.on('done', function(layer) {
map.addLayer(layer);
layer.on('featureOver', function(e, pos, latlng, data) {
$('.leaflet-container').css('cursor','pointer');
if (data.cartodb_id != point.cartodb_id) {
drawHoverPoint(data);
}
cartodb.log.log(pos, data);
});
layer.on('featureOut', function(e, pos, latlng, data) {
$('.leaflet-container').css('cursor','default')
removePoint();
});
layer.on('error', function(err) {
cartodb.log.log('error: ' + err);
});
}).on('error', function() {
cartodb.log.log("some error occurred");
});
Yet when I try to add this layer to a Layer Control:
var clubs = new L.LayerGroup();
clubs.addLayer(clubPts);
I get an "Uncaught TypeError: Object # has no method 'onAdd'" error.
Any thoughts? Thanks!
A great way to reduce complexity and get up to speed quickly here would be using an already-built Leaflet plugin, like Vector Layers, that has built-in CartoDB support already. Take a look at the demo here. http://jasonsanford.github.io/leaflet-vector-layers/demos/cartodb/

Drag from Tree to div

I am trying to implement a drag and drop senario from an extJs TreePanel into a div in the body of the page. I have been following an example by Saki here.
So far I have the below code:
var contentAreas = new Array();
var tree = new Ext.tree.TreePanel({
title : 'Widgets',
useArrows: true,
autoScroll: true,
animate: true,
enableDrag: true,
border: false,
layout:'fit',
ddGroup:'t2div',
loader:new Ext.tree.TreeLoader(),
root:new Ext.tree.AsyncTreeNode({
expanded:true,
leaf:false,
text:'Tree Root',
children:children
}),
listeners:{
startdrag:function() {
$('.content-area').css("outline", "5px solid #FFE767");
},
enddrag:function() {
$('.content-area').css("outline", "0");
}
}
});
var areaDivs = Ext.select('.content-area', true);
Ext.each(areaDivs, function(el) {
var dd = new Ext.dd.DropTarget(el, {
ddGroup:'t2div',
notifyDrop:function(ddt, e, node) {
alert('Drop');
return true;
}
});
contentAreas[contentAreas.length] = dd;
});
The drag begins and the div highlights but when I get over the div it does not show as a valid drop target and the drop fails.
This is my first foray into extJS. I'm JQuery through and through and I am struggling at the moment.
Any help would be appreciated.
Ian
Edit
Furthermore if I create a panel with a drop target in it, this works fine. What is the difference between creating an element and selecting an existing element from the dom. This is obviously where I am going wrong but I'm none the wiser. I have to be able to select existing dom elements and make them into drop targets so the code below is not an option.
Here is the drop target that works
var target = new Ext.Panel({
renderTo: document.body
,layout:'fit'
,id:'target'
,bodyStyle:'font-size:13px'
,title:'Drop Target'
,html:'<div class="drop-target" '
+'style="border:1px silver solid;margin:20px;padding:8px;height:140px">'
+'Drop a node here. I\'m the DropTarget.</div>'
// setup drop target after we're rendered
,afterRender:function() {
Ext.Panel.prototype.afterRender.apply(this, arguments);
this.dropTarget = this.body.child('div.drop-target');
var dd = new Ext.dd.DropTarget(this.dropTarget, {
// must be same as for tree
ddGroup:'t2div'
// what to do when user drops a node here
,notifyDrop:function(dd, e, node) {
alert('drop');
return true;
} // eo function notifyDrop
});
}
});
See if adding true as the second param here makes any difference:
var areaDivs = Ext.select('.content-area', true);
As a cosmetic note, the param name e conventionally indicates an event object (as in the second arg of notifyDrop). For an element, el is more typical. Doesn't matter functionally, but looks weird to someone used to Ext code to see e passed into the DropTarget constructor.
If you are having problem duplicating a working example such as that, copy the entire thing, then modify it to your needs line-by-line - you can't go wrong.
As i know you can't set DropZone to any Ext element, just to Ext component. So this might be you problem. Try to use DropTarget instead of DropZone.