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:
Shannon
2013-11-08 11:48:04 +11:00
parent ef40c76363
commit 7664cec0d3
4 changed files with 220 additions and 44 deletions

View File

@@ -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.

View File

@@ -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(

View File

@@ -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();

View File

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