Fixes: U4-3442 tree sync doesn't work for nodes that are not loaded - tree sync is now working properly and loads in any nodes that are missing. Have removed the dependency on navigationService from umbTree directive since it should not have that dependency. navigationService now sets it's global current node after treeSync based on an event. Moved the treeSync logic to the treeService so it can be unit tested (now to write the unit tests). Added another method to treeService to reload a single node - this is now used by treeSync when the current node is being synced - much nicer than reloading all the children. Updates the treeService to always add the current section to each tree node, this will make it much easier to know the treenode section - and in theory should mean we might be able to remove the ui global currentSection var.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* @name umbraco.directives.directive:umbTree
|
||||
* @restrict E
|
||||
**/
|
||||
function umbTreeDirective($compile, $log, $q, $rootScope, navigationService, treeService, notificationsService, $timeout) {
|
||||
function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificationsService, $timeout) {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -106,7 +106,6 @@ function umbTreeDirective($compile, $log, $q, $rootScope, navigationService, tre
|
||||
|
||||
//reset current node selection
|
||||
scope.currentNode = undefined;
|
||||
navigationService.ui.currentNode = undefined;
|
||||
|
||||
//filter the path for root node ids
|
||||
path = _.filter(path, function(item) { return (item !== "init" && item !== "-1"); });
|
||||
@@ -199,47 +198,22 @@ function umbTreeDirective($compile, $log, $q, $rootScope, navigationService, tre
|
||||
}
|
||||
|
||||
function syncTree(node, path, forceReload) {
|
||||
if (!node || !path || path.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//we are directly above the changed node
|
||||
var onParent = (path.length === 1);
|
||||
var needsReload = true;
|
||||
enableDeleteAnimations = false;
|
||||
|
||||
node.expanded = true;
|
||||
treeService.syncTree({
|
||||
node: node,
|
||||
path: path,
|
||||
forceReload: forceReload
|
||||
}).then(function (data) {
|
||||
scope.currentNode = data;
|
||||
emitEvent("treeSynced", { node: data });
|
||||
//enable delete animations
|
||||
enableDeleteAnimations = true;
|
||||
});
|
||||
|
||||
//if we are not directly above, we will just try to locate
|
||||
//the node and continue down the path
|
||||
if (!onParent) {
|
||||
//if we can find the next node in the path
|
||||
var child = treeService.getChildNode(node, path[0]);
|
||||
if (child) {
|
||||
needsReload = false;
|
||||
path.splice(0, 1);
|
||||
syncTree(child, path, forceReload);
|
||||
}
|
||||
}
|
||||
|
||||
//if a reload is needed, all children will be loaded from server
|
||||
if (needsReload) {
|
||||
scope.loadChildren(node, forceReload)
|
||||
.then(function(children) {
|
||||
var child = treeService.getChildNode(node, path[0]);
|
||||
|
||||
if (!onParent) {
|
||||
path.splice(0, 1);
|
||||
syncTree(child, path, forceReload);
|
||||
}
|
||||
else {
|
||||
navigationService.ui.currentNode = child;
|
||||
scope.currentNode = child;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** method to set the current animation for the node.
|
||||
* This changes dynamically based on if we are changing sections or just loading normal tree data.
|
||||
* When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
|
||||
|
||||
@@ -62,8 +62,8 @@ function treeResource($q, $http, umbRequestHelper) {
|
||||
/** Loads in the data to display the child nodes for a given node */
|
||||
loadNodes: function (options) {
|
||||
|
||||
if (!options || !options.node || !options.section) {
|
||||
throw "The options parameter object does not contain the required properties: 'node' and 'section'";
|
||||
if (!options || !options.node) {
|
||||
throw "The options parameter object does not contain the required properties: 'node'";
|
||||
}
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
|
||||
@@ -194,9 +194,28 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
|
||||
//adding this to get clean global access to the main tree directive
|
||||
//there will only ever be one main tree event handler
|
||||
//we need to pass in the current scope for binding these actions
|
||||
|
||||
//TODO: How many places are we assigning a currentNode?? Now we're assigning a currentNode arbitrarily to this
|
||||
// scope - which looks to be the scope of the navigation controller - but then we are assigning a global current
|
||||
// node on the ui object?? This is a mess.
|
||||
|
||||
setupTreeEvents: function(treeEventHandler, scope) {
|
||||
this.ui.treeEventHandler = treeEventHandler;
|
||||
|
||||
//when a tree node is synced this event will fire, this allows us to set the currentNode
|
||||
this.ui.treeEventHandler.bind("treeSynced", function (ev, args) {
|
||||
|
||||
//set the global current node
|
||||
ui.currentNode = args.node;
|
||||
//not sure what this is doing
|
||||
scope.currentNode = args.node;
|
||||
//what the heck is going on here? - this seems really zany, allowing us to modify the
|
||||
// navigationController.scope from within the navigationService to assign back to the args
|
||||
// so that we can change the navigationController.scope from within the umbTree directive. Hrm.
|
||||
args.scope = scope;
|
||||
|
||||
});
|
||||
|
||||
//this reacts to the options item in the tree
|
||||
this.ui.treeEventHandler.bind("treeOptionsClick", function(ev, args) {
|
||||
ev.stopPropagation();
|
||||
|
||||
@@ -44,9 +44,12 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc
|
||||
//if there is not route path specified, then set it automatically,
|
||||
//if this is a tree root node then we want to route to the section's dashboard
|
||||
if (!treeNodes[i].routePath) {
|
||||
//set the section for each tree node - this allows us to reference this easily when accessing tree nodes
|
||||
treeNodes[i].section = section;
|
||||
|
||||
if (treeNodes[i].metaData && treeNodes[i].metaData["treeAlias"]) {
|
||||
//this is a root node
|
||||
treeNodes[i].routePath = section;
|
||||
treeNodes[i].routePath = section;
|
||||
}
|
||||
else {
|
||||
var treeAlias = this.getTreeAlias(treeNodes[i]);
|
||||
@@ -149,10 +152,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc
|
||||
*/
|
||||
loadNodeChildren: function(args) {
|
||||
if (!args) {
|
||||
throw "No args object defined for getChildren";
|
||||
throw "No args object defined for loadNodeChildren";
|
||||
}
|
||||
if (!args.node) {
|
||||
throw "No node defined on args object for getChildren";
|
||||
throw "No node defined on args object for loadNodeChildren";
|
||||
}
|
||||
|
||||
this.removeChildNodes(args.node);
|
||||
@@ -350,12 +353,192 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc
|
||||
|
||||
var self = this;
|
||||
|
||||
return treeResource.loadNodes({ section: section, node: treeItem })
|
||||
return treeResource.loadNodes({ node: treeItem })
|
||||
.then(function (data) {
|
||||
//now that we have the data, we need to add the level property to each item and the view
|
||||
self._formatNodeDataForUseInUI(treeItem, data, section, treeItem.level + 1);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
/** This re-loads the single node from the server */
|
||||
reloadNode: function(node) {
|
||||
if (!node) {
|
||||
throw "node cannot be null";
|
||||
}
|
||||
if (!node.parent) {
|
||||
throw "cannot reload a single node without a parent";
|
||||
}
|
||||
if (!node.section) {
|
||||
throw "cannot reload a single node without an assigned node.section";
|
||||
}
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
//set the node to loading
|
||||
node.loading = true;
|
||||
|
||||
this.getChildren({ node: node.parent, section: node.section }).then(function(data) {
|
||||
|
||||
//ok, now that we have the children, find the node we're reloading
|
||||
var found = _.find(data, function(item) {
|
||||
return item.id === node.id;
|
||||
});
|
||||
if (found) {
|
||||
//now we need to find the node in the parent.children collection to replace
|
||||
var index = _.indexOf(node.parent.children, node);
|
||||
//the trick here is to not actually replace the node - this would cause the delete animations
|
||||
//to fire, instead we're just going to replace all the properties of this node.
|
||||
_.extend(node.parent.children[index], found);
|
||||
//set the node to loading
|
||||
node.parent.children[index].loading = false;
|
||||
//return
|
||||
deferred.resolve(node.parent.children[index]);
|
||||
}
|
||||
else {
|
||||
deferred.reject();
|
||||
}
|
||||
}, function() {
|
||||
deferred.reject();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
syncTree: function(args) {
|
||||
|
||||
if (!args) {
|
||||
throw "No args object defined for syncTree";
|
||||
}
|
||||
if (!args.node) {
|
||||
throw "No node defined on args object for syncTree";
|
||||
}
|
||||
if (!args.path) {
|
||||
throw "No path defined on args object for syncTree";
|
||||
}
|
||||
//if (!current.metaData["treeAlias"] && !args.node.section) {
|
||||
// throw "No section defined on args.node object for syncTree";
|
||||
//}
|
||||
if (!angular.isArray(args.path)) {
|
||||
throw "Path must be an array";
|
||||
}
|
||||
if (args.path.length < 1) {
|
||||
throw "args.path must contain at least one id";
|
||||
}
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
//get the rootNode for the current node, we'll sync based on that
|
||||
var root = this.getTreeRoot(args.node);
|
||||
if (!root) {
|
||||
throw "Could not get the root tree node based on the node passed in";
|
||||
}
|
||||
|
||||
//now we want to loop through the ids in the path, first we'll check if the first part
|
||||
//of the path is the root node, otherwise we'll search it's children.
|
||||
var currPathIndex = 0;
|
||||
//if the first id is the root node and there's only one... then consider it synced
|
||||
if (String(args.path[currPathIndex]) === String(args.node.id)) {
|
||||
|
||||
if (args.path.length === 1) {
|
||||
//return the root
|
||||
deferred.resolve(root);
|
||||
}
|
||||
else {
|
||||
currPathIndex = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
//now that we have the first id to lookup, we can start the process
|
||||
|
||||
var self = this;
|
||||
var node = args.node;
|
||||
|
||||
var doSync = function() {
|
||||
if (node.children) {
|
||||
//children are loaded, check for the existence
|
||||
var child = self.getChildNode(node, args.path[currPathIndex]);
|
||||
if (child) {
|
||||
if (args.path.length === (currPathIndex + 1)) {
|
||||
//woot! synced the node
|
||||
if (!args.forceReload) {
|
||||
deferred.resolve(child);
|
||||
}
|
||||
else {
|
||||
//even though we've found the node if forceReload is specified
|
||||
//we want to go update this single node from the server
|
||||
self.reloadNode(child).then(function (reloaded) {
|
||||
deferred.resolve(reloaded);
|
||||
}, function() {
|
||||
deferred.reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
//now we need to recurse with the updated node/currPathIndex
|
||||
currPathIndex++;
|
||||
node = child;
|
||||
//recurse
|
||||
doSync();
|
||||
}
|
||||
}
|
||||
else {
|
||||
//we couldn't find the child, if forceReload is true, we can go re-fetch the child collection and try again
|
||||
if (args.forceReload) {
|
||||
self.loadNodeChildren({ node: node.parent }).then(function () {
|
||||
//now we'll check again
|
||||
child = self.getChildNode(node, args.path[currPathIndex]);
|
||||
if (child) {
|
||||
deferred.resolve(child);
|
||||
}
|
||||
else {
|
||||
//fail!
|
||||
deferred.reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
//fail!
|
||||
deferred.reject();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
//the current node doesn't have it's children loaded, so go get them
|
||||
self.loadNodeChildren({ node: node }).then(function () {
|
||||
//ok, got the children, let's find it
|
||||
var found = self.getChildNode(node, args.path[currPathIndex]);
|
||||
if (found) {
|
||||
if (args.path.length === (currPathIndex + 1)) {
|
||||
//woot! synced the node
|
||||
deferred.resolve(found);
|
||||
}
|
||||
else {
|
||||
//now we need to recurse with the updated node/currPathIndex
|
||||
currPathIndex++;
|
||||
node = found;
|
||||
//recurse
|
||||
doSync();
|
||||
}
|
||||
}
|
||||
else {
|
||||
//fail!
|
||||
deferred.reject();
|
||||
}
|
||||
}, function() {
|
||||
//fail!
|
||||
deferred.reject();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//start
|
||||
doSync();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user