GoJS delete child nodes without knowing parent node's key - javascript

I have a goJS diagram with a custom model. When dropping a node on another node, I link them when the mouseDrop fires and set the from and to in the link data on the diagram.model:
mydiagram.model.addLinkData({ from: oldNodeModel.key, to: dragNodeModel.key });
This all works fine. In my node template I have a custom template which puts a panel around the nodes with a delete button. This delete button is simply an image with a click event.
Now when I click the delete image/button, I want to delete this now and all its child nodes.
My issue is I cannot find the children.
I have user events like findNodesOutOf, which yields no results and findNodesConnected which finds parents and child nodes and deletes the lot - which is not what I want.
Any idea how I can solve this?

You can get the item to delete by using the diagram.selection:
var nodeToDelete = mydiagram.selection.iterator.first();
Next to find all the children of this node I recommend a recursive function which will do the following:
Take in the node you want to delete,
Find all the connected nodes to it using mydiagram.getChildrenNodes(nodeToDelete)
Itterrate through the cconnected nodes
Check if each node is a child, by using the linkNodeModel and checking is the link goes from the currentnode to the child node.
Then call the recursive function again with this child node
The recursive function will return an array with all child nodes
Then you can delete them.
Your code will look something like this:
function deleteNode()
{
// TAKE NOTE - This will get all selections so you need to handel this
// If you have multiple select enabled
var nodeToDelete = mydiagram.selection.iterator.first();
var childNodes = getChildNodes(deletedItem);
//Remove linked children
$.each(childNodes, function()
{
myDiagram.remove(this);
});
// Then also delete the actual node after the children was deleted
// TAKE NOTE - This will delete all selections so you need to handle this
// If you have multiple select enabled
mydiagram.commandHandler.deleteSelection();
}
The recursive function keeps checking each node for its children and adds them to an aray:
function getChildNodes(deleteNode)
{
var children = [];
var allConnected= deleteNode.findNodesConnected();
while (allConnected.next())
{
var child = allConnected.value;
// Check to see if this node is a child:
if (isChildNode(deleteNode, child))
{
// add the current child
children.push(child);
// Now call the recursive function again with the current child
// to get its sub children
var subChildren = getChildrenNodes(child);
// add all the children to the children array
$.each(subChildren, function()
{
children.push(this);
});
}
}
// return the children array
return children;
}
This function will check if the node is a child by looking at the links in the diagram and checking to to and from against the current node and child node:
function isChildNode(currNode, currChild)
{
var links = myDiagram.links.iterator;
while (links.next())
{
// Here simply look at the link to determine the direction by checking the direction against the currNode and the child node. If from is the current node and to the child node
// then you know its a vhild
var currentLinkModel = links.value.data;
if (currentLinkModel.from === currNode.data.key && currentLinkModel.to === currChild.data.key)
{
return true;
}
}
return false;
}

Related

Creating new elements at runtime using Mithril

Using Mithril, a Javascript framework, I am trying to add new elements after the initial body has been created and rendered.
Here is my problem in it's most basic form:
let divArray = [];
let newDivButton = m('button', { onclick: ()=> {
divArray.push(m('div', "NEW DIV PUSHED"));
}}, "Add new div");
divArray.push(newDivButton);
divArray.push(m('div', "NEW DIV PUSHED"));
let mainDiv = {
view: () => {
return divArray;
}
}
m.mount(document.body, mainDiv);
The code above will create a button and one line of text saying NEW DIV PUSHED. The button adds one new text element exactly like the 1st one. The problem here is that those additional elements are simply not rendered even if the view function is called. I stepped in the code and clearly see that my divArray is being populated even if they are not rendered.
One thing I noticed is that the initial text element (the one that is rendered) has it's dom property populated by actual div object. All the subsequent text elements in my array have their dom property set to undefined. I don't know how to fix this but I am convinced it is related to my problem.
Mithril has an optimization built into the render lifecycle - it won't re-render a DOM tree if the tree is identical to the last tree. Since divArray === divArray is always true the nodes are never re-rendering.
The simple, but non-ideal solution is to slice your array so you're always returning a new array from mainDiv#view and therefore, Mithril will always re-render the top-level array:
let mainDiv = {
view: () => {
return divArray.slice();
}
};
The more correct way to do this is to map over the data, creating vnodes at the view layer, rather than keeping a list of vnodes statically in your module scope:
let data = ["Data Available Here"];
let mainDiv = {
view: () => {
return [
m(
'button',
{ onclick: () => data.push("Data Pushed Here") },
"Add new div"
);
].concat(
data.map(datum => m('div', datum))
);
}
};

Bind Events Understanding

Just going through some code on a vanilla JS to do list and I'm a bit confused about how this section is working.
The bit that blags my mind a bit is how the checkbox knows which list item is active. I understand we loop over the list items at the beginning and pass them as arguments but how does the checkbox know which one? Aren't we passing multiple list items to the 'taskListItem' argument here?
So when we click the checkbox I'm confused at how it knows which item to use.
Also is this a common way of binding things together? I'm trying to learn patterns so I can fully understand what's going on and can thus build in certain ways rather than just writing any old code.
If someone could break down what's going on here I'd be grateful!
Thanks
var bindTaskEvent = function (taskListItem, checkBoxEventHandler) {
debugger
console.log("Binding Events to Checkbox, Edit, Delete Buttons")
// Select task's <li> children
var checkBox = taskListItem.querySelector("input[type=checkbox]");
var editButton = taskListItem.querySelector("button.edit");
var deleteButton = taskListItem.querySelector("button.delete");
// Bind checkBoxEventHandler to checkbox
checkBox.onchange = checkBoxEventHandler;;
// Bind editTask to Edit button
editButton.onclick = editTask;
// Bind deleteTask to Delete Button
deleteButton.onclick = deleteTask;
};
// TASK COMPLETED
var taskCompleted = function () {
console.log("Running taskCompleted");
var listItem = this.parentNode;
completedTasks.appendChild(listItem);
bindTaskEvent(listItem, taskIncomplete);
}
// TASK INCOMPLETE
var taskIncomplete = function () {
console.log("Running taskIncomplete");
var listItem = this.parentNode;
incompleteTasks.appendChild(listItem);
bindTaskEvent(listItem, taskCompleted);
}
//// WIRING OF THINGS...
// cycle over To Do List items
for (var i = 0; i < incompleteTasks.children.length; i++) {
// bind events to <li>'s children
bindTaskEvent(incompleteTasks.children[i], taskCompleted);
};
// cycle over Completed Items
for (var i = 0; i < completedTasks.children.length; i++) {
// for each list item
// bind event to <li> children (taskCompleted)
bindTaskEvent(completedTasks.children[i], taskIncomplete);
};
Yes, the 'taskListItem' gets multiple list items but the querySelector() method returns the first element that matches a specified CSS selector(s) in the document. Therefore the first element is stored in the variable checkbox not the whole list.

Fancytree addNode function not working inside for loop

I want to move some nodes from one fancy tree to another based on a condition. So in a for loop, for every node I am checking the condition and add it to second fancy tree using addNode() function.
But inside the for loop addNode function is not working.
Here is my forloop code.
'toNodeID' is the id of the fancy tree to which nodes need to be moved. 'selectedValue' is some string value
$.each(selNodes, function (node, selNode) {
if (selNode != null) {
if (toNode.tree == $('#toNodeID').fancytree("getTree")) {
if (selNode.tooltip != selectedValue) {
toNode.addNode(node,'after);
}
}
}
});
You can use
toNode.addChildren(node);
to add a child node of toNode.

Protractor clicking nested elements based on a condition, error - Async callback was not invoked within timeout specified

I am trying to create a Page Object for one of the Reusable UI Controls we are using in our application, which is a table having bunch of headers(th) with buttons to filter. I want to click the button of a particular th element. Here is my code
this.gridAllColumns = browser.element(by.css('[grid-service=envGridService]')).all(by.tagName('th'));
this.filterColumn = function(columnName){
gridAllColumns.each(function(element){
var text = element.getText();
if( text = columnName){
console.log(text);
var buttonElement = element.element(by.tagName('button'));
buttonElement.click();
}
});
}
I am getting the below error
Error: Timeout - Async callback was not invoked within timeout
specified
What I am doing wrong? Could any one point me in right direction please?
You need to refer to the gridAllColumns using this and you need to use filter():
this.filterColumn = function(columnName) {
this.gridAllColumns.filter(function(header) {
return header.getText().then(function (headerText) {
return headerText === columnName;
});
}).first().element(by.tagName('button')).click();
}

Function with memoization, how not to evaluate each time?

I'm writing a simple jQuery function that will swap some HTML elements for others when on certain viewports. The idea is simple:
<div data-swap-for="#element" data-swap-on="phone"></div>
Will insert the element with id #element after that line when the current media query corresponds to phone (the details about how that is done are not important).
My function looks like this:
jq.fn.swapElements = function(viewport) {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
if (targets.length) {
console.log('Found elements to swap for', viewport);
} else {
console.log('Found no elements to swap for', viewport);
}
return {
on: function() {
console.log('Should swap elements for', viewport);
},
off: function() {
console.log('Should restore elements', viewport);
}
}
};
So whenever the screen enters the phone layout, it calls:
jq().swapElements('phone').on();
Which should do all the DOM transformations, and when it exits the phone layout, it calls:
jq().swapElements('phone').off();
Which should restore them.
My problem is that these two are creating a new evaluation of the var targets... part, resulting in:
As the output in the console, and I need this function to cache or remember the variables that it uses, so that the resulting console output is:
> Found elements to swap for phone
> Should swap elements for phone
That is, only evaluating the elements and saving the variables once per each call (a different viewport value should call for a new evaluation).
I've been looking into higher order functions and memoization, but I'm confused about how to apply this in this case and specially to a jQuery function.
Please help?
Thanks
You can use some variable (object or array) to cache already targeted elements.
var cache = {}; // Should be out of function
if (viewport in cache) {
var targets = cache[viewport];
} else {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
cache[viewport] = targets;
}
I Would go with slightly different approach:
jq.fn.swapElements = {
var cache;
getTargets: function(viewport) {
if (viewport in this.cache) {
return cache[viewport];
} else {
var targets = jq('[data-swap-for][data-swap-on='+viewport+']');
if (targets.length) {
console.log('Found elements to swap for', viewport);
} else {
console.log('Found no elements to swap for', viewport);
}
this.cache[viewport] = targets;
return this.cache[viewport];
}
}
on: function(viewport) {
console.log('Should swap elements for', viewport);
},
off: function(viewport) {
console.log('Should restore elements', viewport);
}
};
Pseudocode might not work in particular case, but You get the idea. Whenever You need targets you call swapElements.getTargets(viewport) function.
I'm pretty sure you don't need a higher-order memoize function (although you could trivially apply it when you have written one anyway).
What you need to do is to store the result of jq().swapElements('phone') in a variable, and when the screen enters/exits the phone layout you should call the methods on that variable, instead of creating new instances.

Categories

Resources