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 859ffba037..2d1beeae1a 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 @@ -35,7 +35,42 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS _getTreeCache: function() { return treeCache; }, - + + /** Internal method to track expanded paths on a tree */ + _trackExpandedPaths: function (node, expandedPaths) { + if (!node.children || !angular.isArray(node.children) || node.children.length == 0) { + return; + } + + //take the last child + var childPath = this.getPath(node.children[node.children.length - 1]).join(","); + //check if this already exists, if so exit + if (expandedPaths.indexOf(childPath) !== -1) { + return; + } + + if (expandedPaths.length === 0) { + expandedPaths.push(childPath); //track it + return; + } + + var clonedPaths = expandedPaths.slice(0); //make a copy to iterate over so we can modify the original in the iteration + + _.each(clonedPaths, function (p) { + if (childPath.startsWith(p + ",")) { + //this means that the node's path supercedes this path stored so we can remove the current 'p' and replace it with node.path + expandedPaths.splice(expandedPaths.indexOf(p), 1); //remove it + expandedPaths.push(childPath); //replace it + } + else if (p.startsWith(childPath + ",")) { + //this means we've already tracked a deeper node so we shouldn't track this one + } + else { + expandedPaths.push(childPath); //track it + } + }); + }, + /** Internal method that ensures there's a routePath, parent and level property on each tree node and adds some icon specific properties so that the nodes display properly */ _formatNodeDataForUseInUI: function (parentNode, treeNodes, section, level) { //if no level is set, then we make it 1 diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index f0b3ddb915..a8d6eccf7b 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -9,12 +9,15 @@ * * @param {navigationService} navigationService A reference to the navigationService */ -function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper, languageResource) { +function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, treeService, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper, languageResource) { $scope.treeApi = {}; //Bind to the main tree events $scope.onTreeInit = function () { + + $scope.treeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); + //when a tree is loaded into a section, we need to put it into appState $scope.treeApi.callbacks.treeLoaded(function (args) { appState.setTreeState("currentRootNode", args.tree); @@ -106,11 +109,13 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams, eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi}); } - //Put the navigation service on this scope so we can use it's methods/properties in the view. + //TODO: Remove this, this is not healthy + // Put the navigation service on this scope so we can use it's methods/properties in the view. // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since // when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one. $scope.nav = navigationService; - // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope, + // TODO: Remove this, this is not healthy + // it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope, // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice. $rootScope.nav = navigationService; @@ -127,7 +132,11 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams, $scope.page.languageSelectorIsOpen = false; $scope.currentSection = appState.getSectionState("currentSection"); + $scope.customTreeParams = null; + $scope.treeCacheKey = "_"; $scope.showNavigation = appState.getGlobalState("showNavigation"); + // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths + var expandedPaths = []; //trigger search with a hotkey: keyboardService.bind("ctrl+shift+s", function () { @@ -238,10 +247,77 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams, })); + /** + * Updates the tree's query parameters + */ + function initTree() { + //create the custom query string param for this tree + var queryParams = {}; + if ($scope.selectedLanguage && $scope.selectedLanguage.id) { + queryParams["languageId"] = $scope.selectedLanguage.id; + } + var queryString = $.param(queryParams); //create the query string from the params object + + if (queryString) { + $scope.customTreeParams = queryString; + $scope.treeCacheKey = queryString; // this tree uses caching but we need to change it's cache key per lang + } + else { + $scope.treeCacheKey = "_"; // this tree uses caching, there's no lang selected so use the default + } + } + + function nodeExpandedHandler(args) { + //store the reference to the expanded node path + if (args.node) { + treeService._trackExpandedPaths(args.node, expandedPaths); + } + } + $scope.selectLanguage = function(language, languages) { $scope.selectedLanguage = language; // close the language selector $scope.page.languageSelectorIsOpen = false; + + initTree(); //this will reset the tree params and the tree directive will pick up the changes in a $watch + + //reload the tree with it's updated querystring args + $scope.treeApi.load($scope.currentSection).then(function () { + + //this is sequential promise chaining, it's not pretty but we need to do it this way. $q.all doesn't execute promises in + //sequence but that's what we need to do here + + + //re-sync to currently edited node + var currNode = appState.getTreeState("selectedNode"); + //create the list of promises + var promises = []; + //starting with syncing to the currently selected node if there is one + if (currNode) { + var path = treeService.getPath(currNode); + promises.push($scope.treeApi.syncTree({ path: path, activate: true })); + } + for (var i = 0; i < expandedPaths.length; i++) { + promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); + } + + //now execute them in sequence... sorry there's no other good way to do it with angular promises + var j = 0; + function pExec(promise) { + j++; + promise.then(function (data) { + if (j === promises.length) { + return $q.when(data); //exit + } + else { + return pExec(promises[j]); //recurse + } + }); + } + if (promises.length > 0) { + pExec(promises[0]); //start the promise chain + } + }); }; //this reacts to the options item in the tree diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 29b4710eea..028e27305b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -194,82 +194,46 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", initTree(); //this will reset the tree params and the tree directive will pick up the changes in a $watch - $timeout(function () { //execute in the next digest since the $watch needs to update first + //reload the tree with it's updated querystring args + vm.dialogTreeApi.load(vm.section).then(function () { - //reload the tree with it's updated querystring args - vm.dialogTreeApi.load(vm.section).then(function () { + //this is sequential promise chaining, it's not pretty but we need to do it this way. $q.all doesn't execute promises in + //sequence but that's what we need to do here - //this is sequential promise chaining, it's not pretty but we need to do it this way. $q.all doesn't execute promises in - //sequence but that's what we need to do here + //create the list of promises + var promises = []; + for (var i = 0; i < expandedPaths.length; i++) { + promises.push(vm.dialogTreeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); + } - //create the list of promises - var promises = []; - for (var i = 0; i < expandedPaths.length; i++) { - promises.push(vm.dialogTreeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); - } - - //now execute them in sequence... sorry there's no other good way to do it with angular promises - var j = 0; - function pExec(promise) { - j++; - promise.then(function (data) { - if (j === promises.length) { - return $q.when(data); //exit - } - else { - return pExec(promises[j]); //recurse - } - }); - } + //now execute them in sequence... sorry there's no other good way to do it with angular promises + var j = 0; + function pExec(promise) { + j++; + promise.then(function (data) { + if (j === promises.length) { + return $q.when(data); //exit + } + else { + return pExec(promises[j]); //recurse + } + }); + } + if (promises.length > 0) { pExec(promises[0]); //start the promise chain - }); + } }); }; function toggleLanguageSelector() { vm.languageSelectorIsOpen = !vm.languageSelectorIsOpen; }; - - function trackExpandedPaths(node) { - - if (!node.children || !angular.isArray(node.children) || node.children.length == 0) { - return; - } - - //take the last child - var childPath = treeService.getPath(node.children[node.children.length - 1]).join(","); - //check if this already exists, if so exit - if (expandedPaths.indexOf(childPath) !== -1) { - return; - } - - if (expandedPaths.length === 0) { - expandedPaths.push(childPath); //track it - return; - } - - var clonedPaths = expandedPaths.slice(0); //make a copy to iterate over so we can modify the original in the iteration - - _.each(clonedPaths, function (p) { - if (childPath.startsWith(p + ",")) { - //this means that the node's path supercedes this path stored so we can remove the current 'p' and replace it with node.path - expandedPaths.splice(expandedPaths.indexOf(p), 1); //remove it - expandedPaths.push(childPath); //replace it - } - else if (p.startsWith(childPath + ",")) { - //this means we've already tracked a deeper node so we shouldn't track this one - } - else { - expandedPaths.push(childPath); //track it - } - }); - } - + function nodeExpandedHandler(args) { //store the reference to the expanded node path if (args.node) { - trackExpandedPaths(args.node); + treeService._trackExpandedPaths(args.node, expandedPaths); } // open mini list view for list views diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index 91f1231ae7..0dd3851c6d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -18,10 +18,11 @@