jstree select multiple nodes fire one event - jstree

I have a tree of files and am trying to create an editor for. When one node is selected a panel is loaded for that specific node. If multiple nodes are selected a more generic panel be loaded for all selected nodes.
The problem is, when selecting multiple nodes the "select_node.jstree" is fired for each node.
a snippet of related code...
$("#tree).on("select_node.jstree", function(event, node) {
var selected = node.instance.get_selected();
if(selected.length === 1) {
$("#editor").load(url);
} else if(selected.length > 1) {
$.post(url, {
data: selected
}, function(res) {
$("#editor").html(res);
});
}
});
So... with this if I select 5 items I am doing 1 GET and 4 POSTS.
What I am looking for is 1 GET (the first node selected) and 1 POST (the collection of selected nodes)...
Would it be just a timeout? I feel like I am missing something obvious. I am far from a good programmer, so any direction would be appreciated.

So, I was able to use doTimeout library to manage this. It works, not sure if it is optimal.
https://github.com/cowboy/jquery-dotimeout
$('#tree').on("select_node.jstree", function(event, node) {
$.doTimeout('select', 500, function () {
var selected = node.instance.get_selected();
if(selected.length === 1) {
$('#editor').load(url);
} else if(selected.length > 1) {
$.post(url, {
data: selected
}, function(res) {
$('#editor').html(res);
});
}
});
});

Related

cy.trigger() throws "cy.trigger() can only be called on a single element.in Cypress

For my application, I have to select multiple items and perform drag and drop operation, but I got the following error.
cy.trigger() can only be called on a single element. Your subject contained 2 elements.
cy.get('item1').click();
cy.get('item2')
.click({ctrlkey:true})
.trigger('dragstart',{dataTransfer}))
.trigger('drag',{});
cy.get('targetelement')
.trigger('dragover', { dataTransfer })
.trigger('drop', { dataTransfer })
.trigger('dragend', { dataTransfer });
Every drag and drop has some variation, so this might not work for you. See Issue: Move the cursor (Drag and Drop) #845
There isn't a generic answer for this - it depends on what your app is bound to, and what events and properties it's listening for.
If the dragstart event listener is attached to document you can use
cy.document()
.its('documentElement')
.trigger('dragstart', { dataTransfer: {} });
I used this CodePen Mouse Droppings as a basic multi-select drag-and-drop example app.
It has different events, which I've substituted below your events in the test,
dragover changed to dragenter
drop changed to dragleave
but you will use the events applicable to your app.
Hopefully the key principle cy.document().trigger('dragstart', { dataTransfer: {} }) also works with your app.
My test
it('drags and drops multiple items', () => {
cy.visit('http://127.0.0.1:5500/dist/index.html') // CodePen exported and run locally
cy.contains('Item 0').click(); // select 1st item
cy.contains('Item 2').click({ctrlKey:true}); // also select 3rd item
cy.document()
.its('documentElement')
.trigger('dragstart', { dataTransfer: {} })
cy.get('ol').eq(2) // get the target
// .trigger('dragover', { dataTransfer: {} })
.trigger('dragenter', { dataTransfer: {} })
// .trigger('drop', { dataTransfer: {} })
.trigger('dragleave', { dataTransfer: {} })
.trigger('dragend', { dataTransfer: {} });
cy.get('ol').eq(2).contains('Item 0'); // verify 1st item moved
cy.get('ol').eq(2).contains('Item 2'); // verify 3rd item moved
})
The result is items 0 & 2 from the first list end up on the third list.

How to remove ContentEdit.Static element in ContentTools

I created a new tool in ContentTools that adds a static table (I don't want you to edit).
But being a static element doesn't maintain focus and I can not remove it when I click remove button.
I can do so that the table is not editable but can be removed if you click on it?
That's how I created the element:
new ContentEdit.Static('div', {'data-ce-moveable': true}, '<table><thead><tr><th>Foo head</th></tr></thead><tbody><tr><td>Foo body</td></tr></tbody></table>')
Thank you!
Static elements can't be interacted with for the most part (other elements can be dragged around them but that's about it). ContentEdit/Tools does allow you to restrict the some behaviour for elements but not being able to modify the content of a text element isn't one right now (though I think this might be a worthy addition).
However whilst there's no set way to do this at the moment here's an approach you can use that should provide the behaviour you describe (do let me know how you get on):
ContentEdit.Root.get().bind('mount', function(element) {
// We only care about `TableCell` elements
if (element.type() != 'TableCell') {
return;
}
// We only want to make the element read-only if the parent table has
// the `data-read-only` attribute.
var table = element.closest(function(node) {
return node.type() == 'Table';
});
if (table.attr('data-read-only') !== undefined) {
// Disable text editing for the table cell
element.tableCellText()._onKeyDown = function(ev) {
ev.preventDefault();
}
}
// Disable dragging of the table rows
var tableRow = element.closest(function(node) {
return node.type() == 'TableRow';
});
tableRow.can('drag', false);
tableRow.can('drop', false);
});

Having trouble attaching event listener to a kml layer's polygon

Using Google Earth I have a loaded kml layer that displays polygons of every county in the US. On click a balloon pop's up with some relevant info about the state (name, which state, area, etc) When a user clicks the polygon I want the information to also pop up on a DIV element somewhere else.
This is my code so far.
var ge;
google.load("earth", "1");
function init() {
google.earth.createInstance('map3d', initCB, failureCB);
}
function initCB(instance) {
ge = instance;
ge.getWindow().setVisibility(true);
ge.getNavigationControl().setVisibility(ge.VISIBILITY_AUTO);
ge.getNavigationControl().setStreetViewEnabled(true);
ge.getLayerRoot().enableLayerById(ge.LAYER_ROADS, true);
//here is where im loading the kml file
google.earth.fetchKml(ge, href, function (kmlObject) {
if (kmlObject) {
// show it on Earth
ge.getFeatures().appendChild(kmlObject);
} else {
setTimeout(function () {
alert('Bad or null KML.');
}, 0);
}
});
function recordEvent(event) {
alert("click");
}
// Listen to the mousemove event on the globe.
google.earth.addEventListener(ge.getGlobe(), 'click', recordEvent);
}
function failureCB(errorCode) {}
google.setOnLoadCallback(init);
My problem is that when I change ge.getGlobe() to kmlObject or ge.getFeatures() it doesn't work.
My first question is what should I change ge.getGlobe() to to be able to get a click listener when a user clicks on a kml layer's polygon?
After that I was planning on using getDescription() or getBalloonHtml() to get the polygons balloons information. Am I even on the right track?
...what should I change ge.getGlobe() to...
You don't need to change the event object from GEGlobe. Indeed it is the best option as you can use it to capture all the events and then check the target object in the handler. This means you only have to set up a single event listener in the API.
The other option would be to somehow parse the KML and attach specific event handlers to specific objects. This means you have to create an event listener for each object.
Am I even on the right track?
So, yes you are on the right track. I would keep the generic GEGlobe event listener but extend your recordEvent method to check for the types of KML object you are interested in. You don't show your KML so it is hard to know how you have structured it (are your <Polygon>s nested in <Placemarks> or ` elements for example).
In the simple case if your Polygons are in Placemarks then you could just do the following. Essentially listening for clicks on all objects, then filtering for all Placmark's (either created via the API or loaded in via KML).
function recordEvent(event) {
var target = event.getTarget();
var type = target.getType();
if(type == "KmlPolygon") {
} else if(type == "KmlPlacemark") {
// get the data you want from the target.
var description = target.getDescription();
var balloon = target.getBalloonHtml();
} else if(type == "KmlLineString") {
//etc...
}
};
google.earth.addEventListener(ge.getGlobe(), 'click', recordEvent);
If you wanted to go for the other option you would iterate over the KML Dom once it has loaded and then add events to specific objects. You can do this using something like kmldomwalk.js. Although I wouldn't really recommend this approach here as you will create a large number of event listeners in the api (one for each Placemark in this case). The up side is that the events are attached to each specific object from the kml file, so if you have other Plaemarks, etc, that shouldn't have the same 'click' behaviour then it can be useful.
function placeMarkClick(event) {
var target = event.getTarget();
// get the data you want from the target.
var description = target.getDescription();
var balloon = target.getBalloonHtml();
}
google.earth.fetchKml(ge, href, function (kml) {
if (kml) {
parseKml(kml);
} else {
setTimeout(function () {
alert('Bad or null KML.');
}, 0);
}
});
function parseKml(kml) {
ge.getFeatures().appendChild(kml);
walkKmlDom(kml, function () {
var type = this.getType();
if (type == 'KmlPlacemark') {
// add event listener to `this`
google.earth.addEventListener(this, 'click', placeMarkClick);
}
});
};
Long time since i have worked with this.. but i can try to help you or to give you some tracks...
About your question on "google.earth.addEventListener(ge.getGlobe(), 'click', recordEvent);"
ge.getGlobe can not be replaced with ge.getFeatures() : if you look in the documentation ( https://developers.google.com/earth/documentation/reference/interface_g_e_feature_container-members) for GEFeatureContainer ( which is the output type of getFeatures() , the click Event is not defined!
ge.getGlobe replaced with kmlObject: waht is kmlObject here??
About using getDescription, can you have a look on the getTarget, getCurrentTarget ...
(https://developers.google.com/earth/documentation/reference/interface_kml_event)
As I told you, i haven't work with this since a long time.. so I'm not sure this can help you but at least, it's a first track on which you can look!
Please keep me informed! :-)

Programatically expanding nodes in jstree with ajax load

I have a tree made with jstree which loads partially and loads on via json_data plugin as you expand nodes. Here's the crux of the code:
$("#TreeViewDiv")
.jstree(
{
json_data:
{
ajax:
{
url: "/Website/GetNodes",
data: function (node) {
//do some stuff to compile data for backend here
return {
//insert data for backend here
};
},
error: function () {
$("#TreeViewDiv").html("Error initializing tree");
}
}
},
plugins: ["json_data", "ui"]
});
I then want to expand some of the nodes and select a leaf node, depending on which user is accessing the site. I do this in a loop as follows:
var nodeValues = [Parent, firstChild, leaf];
for (var j = 0; j < nodeValues .length-1; j++) {
$("#TreeViewDiv").jstree("open_node", $("input[value='" + nodeValues [j] + "']"));
}
Opening the Parent node works fine and the firstChild is exposed when the tree is shown but the firstChild node is not open. If I kick off the loop again, the firstchild opens successfully to show the leaf node.
My guess is that the request hasn't completed and the firstChild tree node doesn't exist when the above loop tries to open it. Is there a way to wait for the nodes to load before trying to open the next node? Thank you!
Ok, so I figured it out eventually. Here's a way to do it with deferreds. There's probably a neater way but my head hurts after a day of playing with this so refactoring will have to wait :)
var deffereds = $.Deferred(function (def) { def.resolve(); });
var nodeValues = [Parent, firstChild, leaf];
for (var j = 0; j < nodeValues .length-1; j++) {
deffereds = (function(name, deferreds) {
return deferreds.pipe(function () {
return $.Deferred(function(def) {
$("#TreeViewDiv").jstree("open_node", $("input[value='" + name + "']"), function () {
def.resolve();
});
});
});
})(nodeValues [j], deffereds);
}
This basically places the call to open_node in a deferred and uses the callback from the open_node functions to resolve the deferred thus ensuring that no node is opened before its parent has been opened.

Wanted: Directions/ideas for a custom tree-to-tree drag'n'drop implementation in ExtJS

I need some combined functionality regarding drag'n'drop between two trees in ExtJS.
The first required feature is very simple and is just the builtin drag'n'drop features isolated to a single tree only.
The second required feature is that I wan't the user to be able to drag a node from the left tree and drop it at any node in the right tree.
The action should not remove the node from the left tree, thus creating the possibility of dragging the same node from the left tree to multiple places in the right tree.
My question is: Which approach should I take to combine these two functionalities, utilizing the existing possibilities in the TreePanel object without inventing the wheel again? I am not looking for a complete solution (it would be nice though ;-) ), but rather how to handle drag/drop-zones, events and so on.
Okay. I have been thinking about this some time now, and the following approach seems to work for me :)
I have configured the left tree as this:
listeners:
{
beforenodedrop: function (dropEvent) {
// Does this node come from the right tree?
if (dropEvent.source.tree.id !== dropEvent.tree.id) {
// The node should be discarded.
dropEvent.dropNode.parentNode.removeChild(dropEvent.dropNode, true);
// The node has been discarded, return drop succeeded.
dropEvent.dropStatus = true;
return false;
}
return true;
},
nodedragover: function (dragevent) {
// If the node comes from the right tree, it is allowed to be dropped here.
if (dragevent.source.tree.id !== dragevent.tree.id) {
return true;
}
// A node from this tree is not allowed to be dropped.
return false;
}
}
The right tree is configured like this:
listeners:
{
beforenodedrop: function (dropEvent) {
// Does this node come from the left tree?
if (dropEvent.source.tree.id !== dropEvent.tree.id) {
// The node should be cloned and inserted in the right tree.
// Copy the node.
var node = dropEvent.dropNode; // the node that was dropped
var nodeCopy = new Ext.tree.TreeNode( // copy it
Ext.apply({}, node.attributes)
);
// Match the id's.
nodeCopy.id = Ext.id(null,'newnode') + '_' + node.id;
// Find the right place to put it.
if (dropEvent.target.parentNode === dropEvent.tree.getRootNode()) {
// The node is placed on a folder, thus drop it there.
dropEvent.target.appendChild(nodeCopy);
} else {
// The node is placed inside a folder, thus place it in there.
dropEvent.target.parentNode.appendChild(nodeCopy);
}
// The node has been dropped, return okay and stop further process.
dropEvent.dropStatus = true;
return false;
}
// Just use the normal builtin drag and drop.
return true;
}
}
Both trees has been set to enable Drag'n'Drop:
enableDD: true
All leaf nodes have the following configuration:
allowDrop: true,
draggable: true
All folders are set to:
allowDrop: true,
draggable: false
The conclusion is, that I have chosen to override some of the builtin drag'n'drop methods in the treepanel while still maintaining the builtin functionality.