vis.js removing whole graph when deleting single node - javascript

I'm trying to delete a node from a vis.js graph. When the function is applied, instead of the node and its outgoing edges being deleted the entire graph gets erased. I want the changes to be kept intact when the page gets refreshed. The calls related to this are deleteNode (in index.html) and app.delete (in index.js).
index.html:
var deleteNode = function(data, callback){
$.ajax({
method: "delete",
url: "/api/node",
data: {node: data.nodes[0] },
success: function(result){
callback(result);
}
})
};
index.js:
app.delete("/api/node", function(req, res){
var deleteNode = req.body.node;
var deleteResult = {
nodes:[],
edges: []
};
var updatedNodes = _.filter(data.nodes, function(node){
var keep = (node.id !== deleteNode);
if(!keep){
deleteResult.nodes.push(node);
}
return keep;
});
var updatedEdges = _.filter(data.edges, function(edge){
var keep = (edge.from !== deleteNode) || (edge.to !== deleteNode);
if(!keep){
deleteResult.edges.push(node);
}
return keep;
});
data.nodes = updatedNodes;
data.edges = updatedEdges;
res.send(deleteResult).end();
});

Are you sure that your logic is correct? That is the first thing I would check here. In particular, I see that your 'delete' method seems to be returning the 'deleteResult' variable, which is going to have probably all the nodes that you deleted as a consequence of the API call. Perhaps you wanted to return the 'data' variable instead?

Related

delete entry or update as blank (i.e. " ") to JSON file

I am trying to simply create a delete function, to simply remove the found entry in my JSON file.
So far the below is successfully finding the line (i.e. entry) I want to remove/delete, or update with blank " " - however I am having a hard time writing it back to the JSON file removed... It is fetching just fine, and I am find the line I want to remove just fine.
I believe it is a JavaScript question and something I am mishandling here, as I am using the ../processor.php to write new entries to the JSON file just fine.
// Delete Function
dele.addEventListener('click', ()=>{
let e= document.getElementById("dropdown-of-entry");
let slctn= e.value;
console.log(slctn);
fetch(urld)
.then(
function(response) {
response.json().then(function(data) {
let gData = JSON.stringify(data)
let jsonF = JSON.parse(gData);
let findtempto;
let jsonUpdt;
let jsonUpdtd;
for (let i = 0; i < jsonF.length; i++) {
findtempto = jsonF[i].styleName;
if (findtempto === slctn) {
console.log(jsonF[i]); // This successfully finds the line I want to delete
//delete jsonF[i]; // No avail
jsonF[i] = " "; // Am trying this next
// I have also tried moving the ajax call here, and lines above it
}
}
});
jsonUpdt = JSON.stringify(jsonF);
console.log(jsonUpdt);
jsonUpdtd = JSON.parse(jsonUpdt);
console.log(jsonUpdtd);
$.ajax({
url: './php/data/processor.php',
type: 'POST',
data: { template: jsonUpdtd },
success: function(msg) {
console.log('updated/deleted data');
}
});
}
)
.catch(function(err) {
console.error('Fetch Error -', err);
});
});
Use array.filter() to keep elements in an array that match a criteria.
jsonF = jsonF.filter(e => e.styleName != slctn);
Replacing an object with a string like " " will cause problems later, since the rest of the code expects the array to contain objects.

Redefine "this" within a function prototype and run it with new parameters

I have a function prototype that loads data from a path. The trick is that I need to change the path afterward. I tried call, apply, bind and even assign but as I am a novice I did not find the solution.
Here a sample of my code :
Chat.prototype.loadMessages = function() {
this.messagesRef = this.database;
var setMessage = function(data) {
var val = data.val();
this.displayMessage(data.key, val.name, val.text);
}.bind(this);
};
var chat = new Chat
function setPath (newpath) {
chat.loadMessages.messageRef = newpath; // I guess, it is where I'm wrong...
chat.loadMessages(); // It should load messages from the new path in my chat container.
}
As I said I also tried :
chat.loadMessages.call(newpath);
or
var setPath = function(newpath) {
chat.loadMessages(newpath);
}.bind(chat);
setPath();
chat.loadMessages();
But the chat container continues to disclose messages from the old path...
This looks a bit convoluted. Just pass messagesRef as a parameter and make it default to this.database:
Chat.prototype.loadMessages = function(messagesRef = this.database) {
// do whatever is needed with messagesRef
};
chat = new Chat();
chat.loadMessages(); // load from the default location
chat.loadMessages('foobar'); // load from this specific location
It looks like you are creating a function with loadMessages, which is fine but you need to pass in a value to set the new path. Is this more of what you were thinking?
Chat.prototype.loadMessages = function (newPath) {
this.messagesRef = newPath || this.database; // if newPath is empty than default to this.database
var setMessage = function(data) {
var val = data.val();
this.displayMessage(data.key, val.name, val.text);
};
var chat = new Chat
function setPath (newpath) {
chat.loadMessages(newpath);
}

Angularjs must refresh page to see changes

What I have is simple CRUD operation. Items are listed on page, when user clicks button add, modal pops up, user enters data, and data is saved and should automatically (without refresh)be added to the list on page.
Service:
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).fail(getFailed);
},
addExerciseAndCategories: function(data, initialValues) {
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function(item) {
manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
});
saveChanges().fail(addFailed);
function addFailed() {
removeItem(items, item);
}
},
Controller:
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.queryItems = adminCrudService.querySucceeded(data);
var exerciseIds = _($scope.queryItems).pluck('ExerciseId').uniq().valueOf();
$scope.exerciseAndCategories = [];
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise : exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where($scope.queryItems, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
$scope.items.push(newItem);
});
$scope.$apply();
}
Here is how I add new data from controller:
adminCrudService.addExerciseAndCategories($scope.selectedCategories, { Name: $scope.NewName, Description: $scope.NewDesc });
So my question is, why list isn't updated in real time (when I hit save I must refresh page).
EDIT
Here is my querySuceeded
querySucceeded: function (data) {
items = [];
data.results.forEach(function(item) {
items.push(item);
});
return items;
}
EDIT 2
I believe I've narrowed my problem !
So PW Kad lost two hours with me trying to help me to fix this thing (ad I thank him very very very much for that), but unfortunately with no success. We mostly tried to fix my service, so when I returned to my PC, I've again tried to fix it. I believe my service is fine. (I've made some changes as Kad suggested in his answer).
I believe problem is in controller, I've logged $scope.items, and when I add new item they don't change, after that I've logged $scope.queryItems, and I've noticed that they change after adding new item (without refresh ofc.). So probably problem will be solved by somehow $watching $scope.queryItems after loading initial data, but at the moment I'm not quite sure how to do this.
Alright, I am going to post an answer that should guide you on how to tackle your issue. The issue does not appear to be with Breeze, nor with Angular, but the manner in which you have married the two up. I say this because it is important to understand what you are doing in order to understand the debug process.
Creating an entity adds it to the cache with an entityState of isAdded - that is a true statement, don't think otherwise.
Now for your code...
You don't have to chain your query execution with a promise, but in your case you are returning the data to your controller, and then passing it right back into some function in your service, which wasn't listed in your question. I added a function to replicate what yours probably looks like.
getAllIncluding: function(controllerAction, including) {
var query = breeze.EntityQuery.from(controllerAction).expand(including);
return manager.executeQuery(query).then(querySucceeded).fail(getFailed);
function querySucceeded(data) {
return data.results;
}
},
Now in your controller simply handle the results -
$scope.getAllExercisesAndCategories = function() {
adminCrudService.getAllIncluding("ExercisesAndCategories", "Exercise,ExerciseCategory")
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
// Set your object directly to the data.results, because that is what we are returning from the service
$scope.queryItems = data;
$scope.exerciseAndCategories = [];
Last, let's add the properties we create the entity and see if that gives Angular a chance to bind up properly -
_.forEach(data, function(item) {
var e = manager.createEntity("ExerciseAndCategory");
e.Exercise = addedExercise; e.Category: item.Category;
});
So I've managed to solve my problem ! Not sure if this is right solution but it works now.
I've moved everything to my service, which now looks like this:
function addCategoriesToExercise(tempdata) {
var dataToReturn = [];
var exerciseIds = _(tempdata).pluck('ExerciseId').uniq().valueOf();
var createItem = function (id, exercise) {
return {
ExerciseId: id,
Exercise: exercise,
ExerciseCategories: []
};
};
// cycle through ids
_.forEach(exerciseIds, function (id) {
// get all the queryItems that match
var temp = _.where(tempdata, {
'ExerciseId': id
});
// go to the next if nothing was found.
if (!temp.length) return;
// create a new (clean) item
var newItem = createItem(temp[0].ExerciseId, temp[0].Exercise);
// loop through the queryItems that matched
_.forEach(temp, function (i) {
// if the category has not been added , add it.
if (_.indexOf(newItem.ExerciseCategories, i.ExerciseCategory) < 0) {
newItem.ExerciseCategories.push(i.ExerciseCategory);
}
});
// Add the item to the collection
dataToReturn.push(newItem);
});
return dataToReturn;
}
addExerciseAndCategories: function (data, initialValues) {
newItems = [];
var addedExercise = manager.createEntity("Exercise", initialValues);
_.forEach(data, function (item) {
var entity = manager.createEntity("ExerciseAndCategory", { ExerciseId: addedExercise._backingStore.ExerciseId, CategoryId: item.CategoryId });
items.push(entity);
newItems.push(entity);
});
saveChanges().fail(addFailed);
var itemsToAdd = addCategoriesToExercise(newItems);
_.forEach(itemsToAdd, function (item) {
exerciseAndCategories.push(item);
});
function addFailed() {
removeItem(items, item);
}
}
getAllExercisesAndCategories: function () {
var query = breeze.EntityQuery.from("ExercisesAndCategories").expand("Exercise,ExerciseCategory");
return manager.executeQuery(query).then(getSuceeded).fail(getFailed);
},
function getSuceeded(data) {
items = [];
data.results.forEach(function (item) {
items.push(item);
});
exerciseAndCategories = addCategoriesToExercise(items);
return exerciseAndCategories;
}
And in controller I have only this:
$scope.getAllExercisesAndCategories = function () {
adminExerciseService.getAllExercisesAndCategories()
.then(querySucceeded)
.fail(queryFailed);
};
function querySucceeded(data) {
$scope.items = data;
$scope.$apply();
}

How to rollback nodes that couldn't be moved in jstree

I'm trying to figure out how to rollback only a folder node that wasn't successfully moved. The code below is an example of what I'm trying to do. The problem comes when you have selected a couple of folders and moved them into another folder. If one of the directories fails to be moved I want to be able to roll it back to it's original parent.
Unfortunately $.jstree.rollback(data.rlbk); rollsback all of the folders that were selected to their previous locations.
$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {
// process all selected nodes directory
data.rslt.o.each(function (i) {
// Send request.
var move = $.parseJSON($.ajax({
url: "./jstree.php",
type: 'post',
async: false,
data: {
operation: "move_dir",
....
}
}).responseText);
// When everything's ok, the reponseText will be {success: true}
// In all other cases it won't exist at all.
if(move.success == undefined){
// Here I want to rollback the CURRENT failed node.
// $.jstree.rollback(data.rlbk); will rollback all
// of the directories that have been moved.
}
}
});
Is there a way for this to be done?
I've looked at using jstree before, but haven't used it in my code. As a result, the code may not be correct, but the concepts should be.
Based on your code, it appears that you're performing the move operation on the server side and you want the tree to be updated to reflect the results.
Based on the jsTree documentation, it looks as though you cannot commit node updates and roll back to the last commit.
Instead of rolling back only the changes that you don't want, you can roll back the tree (all changes) and perform the moves afterward.
In order to better understand the code below, you may want to read it (or create a copy) without the lines where "wasTriggeredByCode" is set or referenced in the condition for an "if" statement.
$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {
var jsTree = $(this);
var successes = [];
// Becomes true when function was triggered by code that updates jsTree to
// reflect nodes that were successfully moved on the server
var wasTriggeredByCode = false;
// process all selected nodes directory
data.rslt.o.each(function (i) {
// I'm not certain that this is how the node is referenced
var node = $(this);
wasTriggeredByCode = (wasTriggeredByCode || node.data('redoing'));
// Don't perform server changes when event was triggered from code
if (wasTriggeredByCode) {
return;
}
// Send request.
var move = $.parseJSON($.ajax({
url: "./jstree.php",
type: 'post',
async: false,
data: {
operation: "move_dir",
....
}
}).responseText);
if(move.success){
successes.push(node);
}
});
// Don't continue when event was triggered from code
if (wasTriggeredByCode) {
return;
}
// Roll back the tree here
jsTree.rollback(data.rlbk);
// Move the nodes
for (var i=0; i < successes.length; i++) {
var node = successes[i];
// According to the documentation this will trigger the move event,
// which will result in infinite recursion. To avoid this you'll need
// to set a flag or indicate that you're redoing the move.
node.data('redoing', true);
jsTree.move_node(node, ...);
// Remove the flag so that additional moves aren't ignored
node.removeData('redoing');
}
});
I thought about having something like "onbeforenodemove" event in jstree, something like this:
$("#tree").jstree({...}).bind("before_move_node.jstree", function (e, data) {...}
So I looked inside jstree.js file (version jsTree 3.1.1) and searched for declaration of original "move_node.jstree" handler. It found it declared starting line 3689:
move_node: function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {...}
This function contains the following line at the end of its body:
this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
The above line actually calls your callback declared using .bind("move_node.jstree").
So at the beginning of this function body, I added this:
var before_data = { "node": obj, "parent": new_par.id, "position": pos, "old_parent": old_par, "old_position": old_pos, 'is_multi': (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign': (!old_ins || !old_ins._id), 'old_instance': old_ins, 'new_instance': this, cancelled: false };
this.trigger('before_move_node', before_data);
if (before_data.cancelled) {
return false;
}
Mind "cancelled": false at the end of before_data assigned value.
Also mind inserting the above after new_par, etc. values are assigned.
Code (jsTree instantiation) on my page looks now like this:
$('#tree')
.jstree({
core: {...},
plugins: [...]
})
.bind('before_move_node.jstree', function (e, data) {
if (...) {
data.cancelled = true;
}
})
data object passed to 'before_move_node.jstree' contains the same values that you receive in standard 'move_node.jstree' data argument so you have everything to decide whether you want to cancel the move or let it go. If you decide to cancel, just set the additional 'cancelled' property to true. The entire move will then not happen.
As the documentation says https://github.com/vakata/jstree/wiki#more-on-configuration, you can check more.core property
Example
$('#jstree1').jstree({
core: {
check_callback: async (operation, node, node_parent, node_position, more) => {
switch (true) {
case operation === 'move_node':
let canmove = true
const dropped = more.core === true // not dragging anymore...
if (dropped) {
// before move..
const success = await yourHttpRequest()
if (!success) {
canmove = false
}
} else {
canmove = yourCheckHere()
}
return canmove
}
}
}
})
Example 2
document.addEventListener("DOMContentLoaded", function () {
const bootstrap = (() => {
myTree.mySetup()
})
const myTree = {
mySetup: () => {
$('#jstree1').jstree({
core: {
check_callback: (operation, node, node_parent, node_position, more) => {
switch (true) {
case operation === 'move_node':
return myTree.myGates.canMove(node, node_parent, node_position, more)
}
// deny by default
return false
}
},
plugins: ['dnd']
})
.on('move_node.jstree', (node, parent, position, old_parent, old_position, is_multi, old_instance, new_instance) => {
myTree.myHandlers.onMove({
node, parent, position, old_parent, old_position, is_multi, old_instance, new_instance
})
})
},
myGates: {
canMove: (node, node_parent, node_position, more) => {
const canmove = true
const dropped = more.core === true
if (dropped) {
const success = alberoSx.myHandlers.onBeforeMove({
node, node_parent, node_position, more
})
if (!success) {
canmove = false
}
} else {
canmove = yourCheckHere()
}
return canmove
}
},
myHandlers: {
onBeforeMove: async () => {
// try to update the node in database
const success = await yourHttpRequestHere()
return success
},
onMove: () => {
// node moved in the ui
// do other stuff...
},
}
}
bootstrap()
})

$.getJSON only returns partial and an empty array

I am creating an object to handle the YouTube API and I have two methods:
getCommentList - getting a url for the current upload,for example http://gdata.youtube.com/feeds/api/videos/VIDEO_ID/comments?alt=json and return an array of objects - author of the comment and the content of the comment.
getEntriesObject - returning an array with objects for each upload entry we have title,thumbnail,and the comment list that returned from getCommentList
My jQuery code:
var Youtube = {
getCommentObject : function(url){
if( url ){
var currentCommentFeed = {},
commentsList = [];
$.getJSON(url,function(data){
$.each(data.feed.entry,function(index){
currentCommentFeed = this;
commentsList.push({
author : currentCommentFeed.author[0].name.$t,
content : currentCommentFeed.content.$t
});
});
return commentsList;
});
}
},
getEntriesObject : function(){
var username = 'SOMEYOUTUBEUSERHERE',
url = 'http://gdata.youtube.com/feeds/api/users/' + username + '/uploads?alt=json',
currentEntry = {},
currentObject = {},
entryList = [];
// Scope fix
var that = this;
$.getJSON(url,function(data){
$.each(data.feed.entry, function(index){
// Caching our entry
currentEntry = this;
// Adding our entry title and thumbnail
currentObject = {
title: currentEntry.title.$t
};
if(currentEntry.media$group.media$thumbnail.length == 4)
currentObject['thumbnail'] = currentEntry.media$group.media$thumbnail[3].url;
// Let`s get the comments - undefined....
currentObject['comments'] = that.getCommentObject(currentEntry.gd$comments.gd$feedLink.href + "?alt=json");
console.log(currentObject);
entryList.push(currentObject);
});
});
return entryList;
}
/*
entry[i].title.$t
entry[i].gd$comments.gd$feedLink.href + "?alt=json"
entry[i].media$group.media$thumbnail[3]
// Comments
entry[i].author.name.$t
entry[i].author.content.$t
*/
};
I have console.log(currentObject) and am getting the title. But am not getting the thumbnail URL and the comments.
In addition, when I run getEntriesObject I get back an empty array.
When you call return in the callback to $.getJSON you are returning only that callback function, not the "outer" getCommentObject. Thus when you later call that.getCommentObject you're not getting anything in return (undefined).
getCommentObject: function(url){
if( url ){
// Snip ...
$.getJSON(url,function(data){
// Snip ...
return commentsList; // <- Here
});
}
}
To amend this make getCommentObject take a callback function.
getCommentObject: function(url, callback){
if( url ){
// Snip ...
$.getJSON(url,function(data){
// Snip
// Remove the return statement
callback(commentsList);
});
}
}
Call this function like this:
that.getCommentObject(
currentEntry.gd$comments.gd$feedLink.href + "?alt=json",
function (commentsList) {
currentObject['comments'] = commentsList;
});
Replacing
currentObject['comments'] = that.getCommentObject(currentEntry.gd$comments.gd$feedLink.href + "?alt=json");
You are getting the empty comments because the return statement is in the wrong place. It is in the getJSON callback function. You need to move it from line no 19 to 21 so that it becomes the return statement for getCommentObject. This will fix the first problem. (comments undefined)
Second getEntriesObject is empty because, for some users youtube is returning "Service Unavailable" error for the json request. This happened for when I tried with some random username on youtube.
I checked your program with youtube username "google". After changing the return statement it worked fine.
Hope this helps.

Categories