diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js index c6eaebb1c2..4f68a85ed4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js @@ -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. diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js index 5261e984ff..d004262fe3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/tree.resource.js @@ -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( diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index c74eac6907..3e1ec7b5df 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -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(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index 625dd3741a..3304701072 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -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; + } };