From d45112f2aba7abe2dfa08b0791894190e1c07955 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 17 Apr 2018 01:37:35 +1000 Subject: [PATCH] Gets the content tree controller to return the names for items per language (with mock data), wires up the navigation.controller to re-build the tree for the new language --- .../src/common/services/tree.service.js | 37 +++++++- .../src/controllers/navigation.controller.js | 82 ++++++++++++++++- .../treepicker/treepicker.controller.js | 88 ++++++------------- .../application/umb-navigation.html | 5 +- .../Trees/ContentTreeController.cs | 5 +- .../Trees/ContentTreeControllerBase.cs | 51 ++++++++--- 6 files changed, 186 insertions(+), 82 deletions(-) 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 @@
+ section="{{currentSection}}" + customtreeparams="{{customTreeParams}}">
diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index f723ae17ae..53528484d1 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -47,10 +47,11 @@ namespace Umbraco.Web.Trees /// protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings) { + var langId = queryStrings["languageId"].TryConvertTo(); + var allowedUserOptions = GetAllowedUserMenuItemsForNode(entity); - if (CanUserAccessNode(entity, allowedUserOptions)) + if (CanUserAccessNode(entity, allowedUserOptions, langId.Success ? langId.Result : null)) { - //Special check to see if it ia a container, if so then we'll hide children. var isContainer = entity.IsContainer; // && (queryStrings.Get("isDialog") != "true"); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index dd27c80382..ceb62aedfe 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -14,7 +14,7 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; using System.Globalization; using Umbraco.Core.Models.Entities; -using Umbraco.Web._Legacy.Actions; +using Umbraco.Web._Legacy.Actions; namespace Umbraco.Web.Trees { @@ -148,8 +148,9 @@ namespace Umbraco.Web.Trees } // get child entities - if id is root, but user's start nodes do not contain the - // root node, this returns the start nodes instead of root's children - var entities = GetChildEntities(id).ToList(); + // root node, this returns the start nodes instead of root's children + var langId = queryStrings["languageId"].TryConvertTo(); + var entities = GetChildEntities(id, langId.Success ? langId.Result : null).ToList(); nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, @@ -181,7 +182,7 @@ namespace Umbraco.Web.Trees protected abstract UmbracoObjectTypes UmbracoObjectType { get; } - protected IEnumerable GetChildEntities(string id) + protected IEnumerable GetChildEntities(string id, int? langId) { // try to parse id as an integer else use GetEntityFromId // which will grok Guids, Udis, etc and let use obtain the id @@ -193,17 +194,42 @@ namespace Umbraco.Web.Trees entityId = entity.Id; } + + IEntitySlim[] result; // if a request is made for the root node but user has no access to // root node, return start nodes instead if (entityId == Constants.System.Root && UserStartNodes.Contains(Constants.System.Root) == false) { - return UserStartNodes.Length > 0 - ? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes) - : Enumerable.Empty(); - } - - return Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray(); + result = UserStartNodes.Length > 0 + ? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes).ToArray() + : Array.Empty(); + } + else + { + result = Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray(); + } + + if (langId.HasValue) + { + //need to update all node names + //TODO: This is not currently stored, we need to wait until U4-11128 is complete for this to work, in the meantime + // we'll mock using this and it will just be some mock data + foreach(var e in result) + { + if (e.AdditionalData.TryGetValue("VariantNames", out var variantNames)) + { + var casted = (IDictionary)variantNames; + e.Name = casted[langId.Value]; + } + else + { + e.Name = e.Name + " (lang: " + langId.Value + ")"; + } + } + } + + return result; } /// @@ -362,8 +388,9 @@ namespace Umbraco.Web.Trees /// A list of MenuItems that the user has permissions to execute on the current document /// By default the user must have Browse permissions to see the node in the Content tree /// - internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable allowedUserOptions) - { + internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable allowedUserOptions, int? langId) + { + //TODO: At some stage when we implement permissions on languages we'll need to take care of langId return allowedUserOptions.Select(x => x.Action).OfType().Any(); }