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

This commit is contained in:
Shannon
2018-04-17 01:37:35 +10:00
parent f7719773f2
commit d45112f2ab
6 changed files with 186 additions and 82 deletions
@@ -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
@@ -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
@@ -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
@@ -18,10 +18,11 @@
<!-- the tree -->
<div id="tree" ng-if="authenticated">
<umb-tree
cachekey="_"
cachekey="{{treeCacheKey}}"
api="treeApi"
on-init="onTreeInit()"
section="{{currentSection}}">
section="{{currentSection}}"
customtreeparams="{{customTreeParams}}">
</umb-tree>
</div>
</div>
@@ -47,10 +47,11 @@ namespace Umbraco.Web.Trees
/// <inheritdoc />
protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings)
{
var langId = queryStrings["languageId"].TryConvertTo<int?>();
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");
@@ -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<int?>();
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<IEntitySlim> GetChildEntities(string id)
protected IEnumerable<IEntitySlim> 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<IEntitySlim>();
}
return Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray();
result = UserStartNodes.Length > 0
? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes).ToArray()
: Array.Empty<IEntitySlim>();
}
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<int, string>)variantNames;
e.Name = casted[langId.Value];
}
else
{
e.Name = e.Name + " (lang: " + langId.Value + ")";
}
}
}
return result;
}
/// <summary>
@@ -362,8 +388,9 @@ namespace Umbraco.Web.Trees
/// <param name="allowedUserOptions">A list of MenuItems that the user has permissions to execute on the current document</param>
/// <remarks>By default the user must have Browse permissions to see the node in the Content tree</remarks>
/// <returns></returns>
internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable<MenuItem> allowedUserOptions)
{
internal bool CanUserAccessNode(IUmbracoEntity doc, IEnumerable<MenuItem> 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<ActionBrowse>().Any();
}