diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js
index 84651510be..8f1b53082d 100644
--- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js
+++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js
@@ -90,11 +90,6 @@ Umbraco.Sys.registerNamespace("Umbraco.Application");
//mimic the API of the legacy tree
var tree = {
- setActiveTreeType: function (treeType) {
- angularHelper.safeApply($rootScope, function() {
- navService._setActiveTreeType(treeType);
- });
- },
syncTree: function (path, forceReload) {
angularHelper.safeApply($rootScope, function() {
navService._syncPath(path, forceReload);
@@ -118,9 +113,7 @@ Umbraco.Sys.registerNamespace("Umbraco.Application");
});
},
refreshTree: function (treeAlias) {
- angularHelper.safeApply($rootScope, function() {
- navService._setActiveTreeType(treeAlias, true);
- });
+ //no-op, just needs to be here for legacy reasons
},
moveNode: function (id, path) {
angularHelper.safeApply($rootScope, function() {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js
index 6f4111373d..3a467807a3 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js
@@ -214,7 +214,7 @@ angular.module('umbraco.directives')
};
})
-.directive('onRightClick',function(){
+.directive('onRightClick',function($parse){
document.oncontextmenu = function (e) {
if(e.target.hasAttribute('on-right-click')) {
@@ -228,7 +228,10 @@ angular.module('umbraco.directives')
el.on('contextmenu',function(e){
e.preventDefault();
e.stopPropagation();
- scope.$apply(attrs.onRightClick);
+ var fn = $parse(attrs.onRightClick);
+ scope.$apply(function () {
+ fn(scope, { $event: event });
+ });
return false;
});
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js
index 335d27d5c4..064a34e14b 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js
@@ -20,10 +20,12 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
onlyinitialized: '@',
//Custom query string arguments to pass in to the tree as a string, example: "startnodeid=123&something=value"
customtreeparams: '@',
- eventhandler: '=',
enablecheckboxes: '@',
enablelistviewsearch: '@',
- enablelistviewexpand: '@'
+ enablelistviewexpand: '@',
+
+ api: '=?',
+ onInit: '&?'
},
compile: function (element, attrs) {
@@ -37,7 +39,7 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
'' +
'';
template += '
' +
- '' +
+ '' +
'
' +
'' +
'';
@@ -46,405 +48,384 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
return function (scope, elem, attr, controller) {
- //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
- // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover
- // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the
- // entire tree again since we already still have it in memory. Of course if the section is different we will
- // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times
- // since it saves on data retreival and DOM processing.
- var lastSection = "";
+ };
+ },
+
+ controller: function ($scope, $element) {
+
+ var vm = this;
+
+ var registeredCallbacks = {
+ treeNodeExpanded: [],
+ treeNodeSelect: [],
+ treeLoaded: [],
+ treeSynced: [],
+ treeOptionsClick: [],
+ treeNodeAltSelect: []
+ };
+
+ //this is the API exposed by this directive, for either hosting controllers or for other directives
+ vm.callbacks = {
+ treeNodeExpanded: function (f) {
+ registeredCallbacks.treeNodeExpanded.push(f);
+ },
+ treeNodeSelect: function (f) {
+ registeredCallbacks.treeNodeSelect.push(f);
+ },
+ treeLoaded: function (f) {
+ registeredCallbacks.treeLoaded.push(f);
+ },
+ treeSynced: function (f) {
+ registeredCallbacks.treeSynced.push(f);
+ },
+ treeOptionsClick: function (f) {
+ registeredCallbacks.treeOptionsClick.push(f);
+ },
+ treeNodeAltSelect: function (f) {
+ registeredCallbacks.treeNodeAltSelect.push(f);
+ }
+ };
+ vm.emitEvent = emitEvent;
+ vm.load = load;
+ vm.reloadNode = reloadNode;
+ vm.syncTree = syncTree;
+
+ //wire up the exposed api object for hosting controllers
+ if ($scope.api) {
+ $scope.api.callbacks = vm.callbacks;
+ $scope.api.load = vm.load;
+ $scope.api.reloadNode = vm.reloadNode;
+ $scope.api.syncTree = vm.syncTree;
+ }
+
+ //flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
+ // re-load the tree again. For example, if we hover over 'content' the content tree is shown. Then we hover
+ // outside of the tree and the tree 'un-loads'. When we re-hover over 'content', we don't want to re-load the
+ // entire tree again since we already still have it in memory. Of course if the section is different we will
+ // reload it. This saves a lot on processing if someone is navigating in and out of the same section many times
+ // since it saves on data retreival and DOM processing.
+ var lastSection = "";
+
+ //flag to enable/disable delete animations
+ var deleteAnimations = false;
- //setup a default internal handler
- if (!scope.eventhandler) {
- scope.eventhandler = $({});
+ /** Helper function to emit tree events */
+ function emitEvent(eventName, args) {
+ if (registeredCallbacks[eventName] && angular.isArray(registeredCallbacks[eventName])) {
+ _.each(registeredCallbacks[eventName], function (c) {
+ c(args);//call it
+ });
+ }
+ }
+
+ /** This will deleteAnimations to true after the current digest */
+ function enableDeleteAnimations() {
+ //do timeout so that it re-enables them after this digest
+ $timeout(function () {
+ //enable delete animations
+ deleteAnimations = true;
+ }, 0, false);
+ }
+
+ function clearCache(section) {
+ treeService.clearCache({ section: section });
+ }
+
+ function load(section) {
+ $scope.section = section;
+ return loadTree();
+ }
+
+ function reloadNode(node) {
+
+ if (!node) {
+ node = $scope.currentNode;
}
- //flag to enable/disable delete animations
- var deleteAnimations = false;
+ if (node) {
+ return $scope.loadChildren(node, true);
+ }
+
+ return $q.reject();
+ }
+
+ /**
+ * Used to do the tree syncing
+ * @param {any} args
+ * @returns a promise with an object containing 'node' and 'activate'
+ */
+ function syncTree(args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.path) {
+ throw "args.path cannot be null";
+ }
+
+ var treeNode = loadActiveTree(args.tree);
+
+ if (angular.isString(args.path)) {
+ args.path = args.path.replace('"', '').split(',');
+ }
+ //Filter the path for root node ids (we don't want to pass in -1 or 'init')
- /** Helper function to emit tree events */
- function emitEvent(eventName, args) {
- if (scope.eventhandler) {
- $(scope.eventhandler).trigger(eventName, args);
+ args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); });
+
+ //Once those are filtered we need to check if the current user has a special start node id,
+ // if they do, then we're going to trim the start of the array for anything found from that start node
+ // and previous so that the tree syncs properly. The tree syncs from the top down and if there are parts
+ // of the tree's path in there that don't actually exist in the dom/model then syncing will not work.
+
+ return userService.getCurrentUser().then(function (userData) {
+
+ var startNodes = [];
+ for (var i = 0; i < userData.startContentIds; i++) {
+ startNodes.push(userData.startContentIds[i]);
}
- }
-
- /** This will deleteAnimations to true after the current digest */
- function enableDeleteAnimations() {
- //do timeout so that it re-enables them after this digest
- $timeout(function () {
- //enable delete animations
- deleteAnimations = true;
- }, 0, false);
- }
-
-
- /*this is the only external interface a tree has */
- function setupExternalEvents() {
- if (scope.eventhandler) {
-
- scope.eventhandler.clearCache = function (section) {
- treeService.clearCache({ section: section });
- };
-
- scope.eventhandler.load = function (section) {
- scope.section = section;
- loadTree();
- };
-
- scope.eventhandler.reloadNode = function (node) {
-
- if (!node) {
- node = scope.currentNode;
- }
-
- if (node) {
- scope.loadChildren(node, true);
- }
- };
-
- /**
- Used to do the tree syncing. If the args.tree is not specified we are assuming it has been
- specified previously using the _setActiveTreeType
- */
- scope.eventhandler.syncTree = function (args) {
- if (!args) {
- throw "args cannot be null";
- }
- if (!args.path) {
- throw "args.path cannot be null";
- }
-
- var deferred = $q.defer();
-
- //this is super complex but seems to be working in other places, here we're listening for our
- // own events, once the tree is sycned we'll resolve our promise.
- scope.eventhandler.one("treeSynced", function (e, syncArgs) {
- deferred.resolve(syncArgs);
- });
-
- //this should normally be set unless it is being called from legacy
- // code, so set the active tree type before proceeding.
- if (args.tree) {
- loadActiveTree(args.tree);
- }
-
- if (angular.isString(args.path)) {
- args.path = args.path.replace('"', '').split(',');
- }
-
- //reset current node selection
- //scope.currentNode = null;
-
- //Filter the path for root node ids (we don't want to pass in -1 or 'init')
-
- args.path = _.filter(args.path, function (item) { return (item !== "init" && item !== "-1"); });
-
- //Once those are filtered we need to check if the current user has a special start node id,
- // if they do, then we're going to trim the start of the array for anything found from that start node
- // and previous so that the tree syncs properly. The tree syncs from the top down and if there are parts
- // of the tree's path in there that don't actually exist in the dom/model then syncing will not work.
-
- userService.getCurrentUser().then(function (userData) {
-
- var startNodes = [];
- for (var i = 0; i < userData.startContentIds; i++) {
- startNodes.push(userData.startContentIds[i]);
- }
- for (var j = 0; j < userData.startMediaIds; j++) {
- startNodes.push(userData.startMediaIds[j]);
- }
-
- _.each(startNodes, function (i) {
- var found = _.find(args.path, function (p) {
- return String(p) === String(i);
- });
- if (found) {
- args.path = args.path.splice(_.indexOf(args.path, found));
- }
- });
-
-
- loadPath(args.path, args.forceReload, args.activate);
-
- });
-
-
-
- return deferred.promise;
- };
-
- /**
- Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
- have to set an active tree and then sync, the new API does this in one method by using syncTree.
- loadChildren is optional but if it is set, it will set the current active tree and load the root
- node's children - this is synonymous with the legacy refreshTree method - again should not be used
- and should only be used for the legacy code to work.
- */
- scope.eventhandler._setActiveTreeType = function (treeAlias, loadChildren) {
- loadActiveTree(treeAlias, loadChildren);
- };
+ for (var j = 0; j < userData.startMediaIds; j++) {
+ startNodes.push(userData.startMediaIds[j]);
}
- }
-
- //helper to load a specific path on the active tree as soon as its ready
- function loadPath(path, forceReload, activate) {
-
- if (scope.activeTree) {
- syncTree(scope.activeTree, path, forceReload, activate);
- }
- else {
- scope.eventhandler.one("activeTreeLoaded", function (e, args) {
- syncTree(args.tree, path, forceReload, activate);
+ _.each(startNodes, function (i) {
+ var found = _.find(args.path, function (p) {
+ return String(p) === String(i);
});
- }
- }
-
-
- //given a tree alias, this will search the current section tree for the specified tree alias and
- //set that to the activeTree
- //NOTE: loadChildren is ONLY used for legacy purposes, do not use this when syncing the tree as it will cause problems
- // since there will be double request and event handling operations.
- function loadActiveTree(treeAlias, loadChildren) {
- if (!treeAlias) {
- return;
- }
-
- scope.activeTree = undefined;
-
- function doLoad(tree) {
- var childrenAndSelf = [tree].concat(tree.children);
- scope.activeTree = _.find(childrenAndSelf, function (node) {
- if (node && node.metaData && node.metaData.treeAlias) {
- return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase();
- }
- return false;
- });
-
- if (!scope.activeTree) {
- throw "Could not find the tree " + treeAlias + ", activeTree has not been set";
+ if (found) {
+ args.path = args.path.splice(_.indexOf(args.path, found));
}
-
- //This is only used for the legacy tree method refreshTree!
- if (loadChildren) {
- scope.activeTree.expanded = true;
- scope.loadChildren(scope.activeTree, false).then(function () {
- emitEvent("activeTreeLoaded", { tree: scope.activeTree });
- });
- }
- else {
- emitEvent("activeTreeLoaded", { tree: scope.activeTree });
- }
- }
-
- if (scope.tree) {
- doLoad(scope.tree.root);
- }
- else {
- scope.eventhandler.one("treeLoaded", function (e, args) {
- doLoad(args.tree.root);
- });
- }
- }
-
-
- /** Method to load in the tree data */
-
- function loadTree() {
- if (!scope.loading && scope.section) {
- scope.loading = true;
-
- //anytime we want to load the tree we need to disable the delete animations
- deleteAnimations = false;
-
- //default args
- var args = { section: scope.section, tree: scope.treealias, cacheKey: scope.cachekey, isDialog: scope.isdialog ? scope.isdialog : false, onlyinitialized: scope.onlyinitialized };
-
- //add the extra query string params if specified
- if (scope.customtreeparams) {
- args["queryString"] = scope.customtreeparams;
- }
-
- treeService.getTree(args)
- .then(function (data) {
- //set the data once we have it
- scope.tree = data;
-
- enableDeleteAnimations();
-
- scope.loading = false;
-
- //set the root as the current active tree
- scope.activeTree = scope.tree.root;
- emitEvent("treeLoaded", { tree: scope.tree });
- emitEvent("treeNodeExpanded", { tree: scope.tree, node: scope.tree.root, children: scope.tree.root.children });
-
- }, function (reason) {
- scope.loading = false;
- notificationsService.error("Tree Error", reason);
- });
- }
- }
-
- /** syncs the tree, the treeNode can be ANY tree node in the tree that requires syncing */
- function syncTree(treeNode, path, forceReload, activate) {
-
+ });
+
deleteAnimations = false;
- treeService.syncTree({
+ return treeService.syncTree({
node: treeNode,
- path: path,
- forceReload: forceReload
+ path: args.path,
+ forceReload: args.forceReload
}).then(function (data) {
- if (activate === undefined || activate === true) {
- scope.currentNode = data;
+ if (args.activate === undefined || args.activate === true) {
+ $scope.currentNode = data;
}
- emitEvent("treeSynced", { node: data, activate: activate });
+ emitEvent("treeSynced", { node: data, activate: args.activate });
- enableDeleteAnimations();
+ enableDeleteAnimations();
+
+ return $q.when({ node: data, activate: args.activate });
});
+ });
+
+ }
+ //given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node
+ function loadActiveTree(treeAlias) {
+
+ if (!$scope.tree) {
+ throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null";
+ }
+
+ //if its not specified, it should have been specified before
+ if (!treeAlias) {
+ if (!$scope.activeTree) {
+ throw "Err in umbtree.directive.loadActiveTree, $scope.activeTree is null";
+ }
+ return $scope.activeTree;
+ }
+
+ var childrenAndSelf = [$scope.tree.root].concat($scope.tree.root.children);
+ $scope.activeTree = _.find(childrenAndSelf, function (node) {
+ if (node && node.metaData && node.metaData.treeAlias) {
+ return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase();
+ }
+ return false;
+ });
+
+ if (!$scope.activeTree) {
+ throw "Could not find the tree " + treeAlias;
}
- /** Returns the css classses assigned to the node (div element) */
- scope.getNodeCssClass = function (node) {
- if (!node) {
- return '';
+ emitEvent("activeTreeLoaded", { tree: $scope.activeTree });
+
+ return $scope.activeTree;
+ }
+
+ /** Method to load in the tree data */
+
+ function loadTree() {
+ if (!$scope.loading && $scope.section) {
+ $scope.loading = true;
+
+ //anytime we want to load the tree we need to disable the delete animations
+ deleteAnimations = false;
+
+ //default args
+ var args = { section: $scope.section, tree: $scope.treealias, cacheKey: $scope.cachekey, isDialog: $scope.isdialog ? $scope.isdialog : false, onlyinitialized: $scope.onlyinitialized };
+
+ //add the extra query string params if specified
+ if ($scope.customtreeparams) {
+ args["queryString"] = $scope.customtreeparams;
}
- //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time
- // it would be better if we could cache the processing. The problem is that some of these things are dynamic.
+ return treeService.getTree(args)
+ .then(function (data) {
+ //set the data once we have it
+ $scope.tree = data;
- var css = [];
- if (node.cssClasses) {
- _.each(node.cssClasses, function (c) {
- css.push(c);
+ enableDeleteAnimations();
+
+ $scope.loading = false;
+
+ //set the root as the current active tree
+ $scope.activeTree = $scope.tree.root;
+
+ emitEvent("treeLoaded", { tree: $scope.tree });
+ emitEvent("treeNodeExpanded", { tree: $scope.tree, node: $scope.tree.root, children: $scope.tree.root.children });
+ return $q.when(data);
+ }, function (reason) {
+ $scope.loading = false;
+ notificationsService.error("Tree Error", reason);
+ return $q.reject(reason);
});
- }
-
- return css.join(" ");
- };
+ }
+ else {
+ return $q.reject();
+ }
+ }
- scope.selectEnabledNodeClass = function (node) {
- return node ?
- node.selected ?
- 'icon umb-tree-icon sprTree icon-check green temporary' :
- '' :
- '';
- };
+ /** Returns the css classses assigned to the node (div element) */
+ $scope.getNodeCssClass = function (node) {
+ if (!node) {
+ return '';
+ }
- /** 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.
- */
- scope.animation = function () {
- if (deleteAnimations && scope.tree && scope.tree.root && scope.tree.root.expanded) {
- return { leave: 'tree-node-delete-leave' };
- }
- else {
- return {};
- }
- };
+ //TODO: This is called constantly because as a method in a template it's re-evaluated pretty much all the time
+ // it would be better if we could cache the processing. The problem is that some of these things are dynamic.
- /* helper to force reloading children of a tree node */
- scope.loadChildren = function (node, forceReload) {
- var deferred = $q.defer();
+ var css = [];
+ if (node.cssClasses) {
+ _.each(node.cssClasses, function (c) {
+ css.push(c);
+ });
+ }
- //emit treeNodeExpanding event, if a callback object is set on the tree
- emitEvent("treeNodeExpanding", { tree: scope.tree, node: node });
-
- //standardising
- if (!node.children) {
- node.children = [];
- }
-
- if (forceReload || (node.hasChildren && node.children.length === 0)) {
- //get the children from the tree service
- treeService.loadNodeChildren({ node: node, section: scope.section })
- .then(function (data) {
- //emit expanded event
- emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data });
-
- enableDeleteAnimations();
-
- deferred.resolve(data);
- });
- }
- else {
- emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children });
- node.expanded = true;
-
- enableDeleteAnimations();
-
- deferred.resolve(node.children);
- }
-
- return deferred.promise;
- };
-
- /**
- Method called when the options button next to the root node is called.
- The tree doesnt know about this, so it raises an event to tell the parent controller
- about it.
- */
- scope.options = function (n, ev) {
- emitEvent("treeOptionsClick", { element: elem, node: n, event: ev });
- };
-
- /**
- Method called when an item is clicked in the tree, this passes the
- DOM element, the tree node object and the original click
- and emits it as a treeNodeSelect element if there is a callback object
- defined on the tree
- */
- scope.select = function (n, ev) {
-
- if (n.metaData && n.metaData.noAccess === true) {
- ev.preventDefault();
- return;
- }
-
- //on tree select we need to remove the current node -
- // whoever handles this will need to make sure the correct node is selected
- //reset current node selection
- scope.currentNode = null;
-
- emitEvent("treeNodeSelect", { element: elem, node: n, event: ev });
- };
-
- scope.altSelect = function (n, ev) {
- emitEvent("treeNodeAltSelect", { element: elem, tree: scope.tree, node: n, event: ev });
- };
-
- //watch for section changes and customtreeparams changes
- scope.$watchCollection("[section, customtreeparams]", function (newVal, oldVal) {
-
- if (!scope.tree) {
- loadTree();
- }
-
- if (!newVal) {
- //store the last section loaded
- lastSection = oldVal;
- }
- else if (newVal !== oldVal && newVal !== lastSection) {
- //only reload the tree data and Dom if the newval is different from the old one
- // and if the last section loaded is different from the requested one.
- loadTree();
-
- //store the new section to be loaded as the last section
- //clear any active trees to reset lookups
- lastSection = newVal;
- }
- });
-
- setupExternalEvents();
- loadTree();
+ return css.join(" ");
};
+
+ $scope.selectEnabledNodeClass = function (node) {
+ return node ?
+ node.selected ?
+ 'icon umb-tree-icon sprTree icon-check green temporary' :
+ '' :
+ '';
+ };
+
+ /** 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.
+ */
+ $scope.animation = function () {
+ if (deleteAnimations && $scope.tree && $scope.tree.root && $scope.tree.root.expanded) {
+ return { leave: 'tree-node-delete-leave' };
+ }
+ else {
+ return {};
+ }
+ };
+
+ /* helper to force reloading children of a tree node */
+ $scope.loadChildren = function (node, forceReload) {
+
+ //emit treeNodeExpanding event, if a callback object is set on the tree
+ emitEvent("treeNodeExpanding", { tree: $scope.tree, node: node });
+
+ //standardising
+ if (!node.children) {
+ node.children = [];
+ }
+
+ if (forceReload || (node.hasChildren && node.children.length === 0)) {
+ //get the children from the tree service
+ return treeService.loadNodeChildren({ node: node, section: $scope.section })
+ .then(function (data) {
+ //emit expanded event
+ emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: data });
+
+ enableDeleteAnimations();
+
+ return $q.when(data);
+ });
+ }
+ else {
+ emitEvent("treeNodeExpanded", { tree: $scope.tree, node: node, children: node.children });
+ node.expanded = true;
+
+ enableDeleteAnimations();
+
+ return $q.when(node.children);
+ }
+ };
+
+ /**
+ Method called when the options button next to the root node is called.
+ The tree doesnt know about this, so it raises an event to tell the parent controller
+ about it.
+ */
+ $scope.options = function (n, ev) {
+ emitEvent("treeOptionsClick", { element: $element, node: n, event: ev });
+ };
+
+ /**
+ Method called when an item is clicked in the tree, this passes the
+ DOM element, the tree node object and the original click
+ and emits it as a treeNodeSelect element if there is a callback object
+ defined on the tree
+ */
+ $scope.select = function (n, ev) {
+
+ if (n.metaData && n.metaData.noAccess === true) {
+ ev.preventDefault();
+ return;
+ }
+
+ //on tree select we need to remove the current node -
+ // whoever handles this will need to make sure the correct node is selected
+ //reset current node selection
+ $scope.currentNode = null;
+
+ emitEvent("treeNodeSelect", { element: $element, node: n, event: ev });
+ };
+
+ $scope.altSelect = function (n, ev) {
+ emitEvent("treeNodeAltSelect", { element: $element, tree: $scope.tree, node: n, event: ev });
+ };
+
+ //watch for section changes
+ $scope.$watch("section", function (newVal, oldVal) {
+
+ if (!$scope.tree) {
+ loadTree();
+ }
+
+ if (!newVal) {
+ //store the last section loaded
+ lastSection = oldVal;
+ }
+ else if (newVal !== oldVal && newVal !== lastSection) {
+ //only reload the tree data and Dom if the newval is different from the old one
+ // and if the last section loaded is different from the requested one.
+ loadTree();
+
+ //store the new section to be loaded as the last section
+ //clear any active trees to reset lookups
+ lastSection = newVal;
+ }
+ });
+
+ //call the callback, this allows for hosting controllers to bind to events and use the exposed API
+ $scope.onInit();
+
+ loadTree();
}
};
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js
index 0bb888a59f..edd6e9d608 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js
@@ -22,14 +22,13 @@ angular.module("umbraco.directives")
return {
restrict: 'E',
replace: true,
-
+ require: '^umbTree',
scope: {
section: '@',
- eventhandler: '=',
currentNode: '=',
enablelistviewexpand: '@',
node: '=',
- tree: '='
+ tree: '=' //TODO: Not sure we need this since we are 'require' on the umbTree
},
//TODO: Remove more of the binding from this template and move the DOM manipulation to be manually done in the link function,
@@ -48,7 +47,7 @@ angular.module("umbraco.directives")
'' +
'',
- link: function (scope, element, attrs) {
+ link: function (scope, element, attrs, umbTreeCtrl) {
localizationService.localize("general_search").then(function (value) {
scope.searchAltText = value;
@@ -57,14 +56,6 @@ angular.module("umbraco.directives")
//flag to enable/disable delete animations, default for an item is true
var deleteAnimations = true;
- // Helper function to emit tree events
- function emitEvent(eventName, args) {
-
- if (scope.eventhandler) {
- $(scope.eventhandler).trigger(eventName, args);
- }
- }
-
// updates the node's DOM/styles
function setupNodeDom(node, tree) {
@@ -153,7 +144,7 @@ angular.module("umbraco.directives")
about it.
*/
scope.options = function (n, ev) {
- emitEvent("treeOptionsClick", { element: element, tree: scope.tree, node: n, event: ev });
+ umbTreeCtrl.emitEvent("treeOptionsClick", { element: element, tree: scope.tree, node: n, event: ev });
};
/**
@@ -176,7 +167,7 @@ angular.module("umbraco.directives")
return;
}
- emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev });
+ umbTreeCtrl.emitEvent("treeNodeSelect", { element: element, tree: scope.tree, node: n, event: ev });
ev.preventDefault();
};
@@ -187,7 +178,7 @@ angular.module("umbraco.directives")
defined on the tree
*/
scope.altSelect = function (n, ev) {
- emitEvent("treeNodeAltSelect", { element: element, tree: scope.tree, node: n, event: ev });
+ umbTreeCtrl.emitEvent("treeNodeAltSelect", { element: element, tree: scope.tree, node: n, event: ev });
};
/** method to set the current animation for the node.
@@ -214,7 +205,7 @@ angular.module("umbraco.directives")
scope.load = function (node) {
if (node.expanded && !node.metaData.isContainer) {
deleteAnimations = false;
- emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element });
+ umbTreeCtrl.emitEvent("treeNodeCollapsing", { tree: scope.tree, node: node, element: element });
node.expanded = false;
}
else {
@@ -225,19 +216,19 @@ angular.module("umbraco.directives")
/* helper to force reloading children of a tree node */
scope.loadChildren = function (node, forceReload) {
//emit treeNodeExpanding event, if a callback object is set on the tree
- emitEvent("treeNodeExpanding", { tree: scope.tree, node: node });
+ umbTreeCtrl.emitEvent("treeNodeExpanding", { tree: scope.tree, node: node });
if (node.hasChildren && (forceReload || !node.children || (angular.isArray(node.children) && node.children.length === 0))) {
//get the children from the tree service
treeService.loadNodeChildren({ node: node, section: scope.section })
.then(function (data) {
//emit expanded event
- emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data });
+ umbTreeCtrl.emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: data });
enableDeleteAnimations();
});
}
else {
- emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children });
+ umbTreeCtrl.emitEvent("treeNodeExpanded", { tree: scope.tree, node: node, children: node.children });
node.expanded = true;
enableDeleteAnimations();
}
@@ -253,7 +244,7 @@ angular.module("umbraco.directives")
scope.loadChildren(scope.node);
}
- var template = '
';
+ var template = '
';
var newElement = angular.element(template);
$compile(newElement)(scope);
element.append(newElement);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js
index fb4d8216e3..24db2e5989 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js
@@ -8,7 +8,42 @@
*/
function angularHelper($log, $q) {
return {
-
+
+ /**
+ * Execute a list of promises sequentially. Unlike $q.all which executes all promises at once, this will execute them in sequence.
+ * @param {} promises
+ * @returns {}
+ */
+ executeSequentialPromises: function (promises) {
+
+ //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 want to do here.
+
+ if (!angular.isArray(promises)) {
+ throw "promises must be an array";
+ }
+
+ //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++;
+ return promise.then(function (data) {
+ if (j === promises.length) {
+ return $q.when(data); //exit
+ }
+ else {
+ return pExec(promises[j]); //recurse
+ }
+ });
+ }
+ if (promises.length > 0) {
+ return pExec(promises[0]); //start the promise chain
+ }
+ else {
+ return $q.when(true); // just exit, no promises to execute
+ }
+ },
+
/**
* @ngdoc function
* @name safeApply
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js
index e28970336f..74b9abdb6a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/events.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/events.service.js
@@ -9,7 +9,8 @@
app.closeDialogs
app.ysod
app.reInitialize
- app.userRefresh
+ app.userRefresh
+ app.navigationReady
*/
function eventsService($q, $rootScope) {
@@ -42,4 +43,4 @@ function eventsService($q, $rootScope) {
};
}
-angular.module('umbraco.services').factory('eventsService', eventsService);
\ No newline at end of file
+angular.module('umbraco.services').factory('eventsService', eventsService);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js
index 7200e1f6f8..d9e95ff309 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js
@@ -8,7 +8,7 @@
* @description
* Defines the methods that are called when menu items declare only an action to execute
*/
-function umbracoMenuActions($q, treeService, $location, navigationService, appState, localizationService, userResource, umbRequestHelper, notificationsService) {
+function umbracoMenuActions(treeService, $location, navigationService, appState, localizationService, usersResource, umbRequestHelper, notificationsService) {
return {
@@ -34,7 +34,7 @@ function umbracoMenuActions($q, treeService, $location, navigationService, appSt
localizationService.localize("defaultdialogs_confirmdisable").then(function (txtConfirmDisable) {
var currentMenuNode = UmbClientMgr.mainTree().getActionNode();
if (confirm(txtConfirmDisable + ' "' + args.entity.name + '"?\n\n')) {
- userResource.disableUser(args.entity.id).then(function () {
+ usersResource.disableUser(args.entity.id).then(function () {
navigationService.syncTree({ tree: args.treeAlias, path: [args.entity.parentId, args.entity.id], forceReload: true });
});
}
@@ -103,4 +103,4 @@ function umbracoMenuActions($q, treeService, $location, navigationService, appSt
};
}
-angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions);
\ No newline at end of file
+angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions);
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 d3250ee64b..257d36af31 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
@@ -15,14 +15,18 @@
* Section navigation and search, and maintain their state for the entire application lifetime
*
*/
-function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) {
+function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, eventsService, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) {
+ //the main tree's API reference, this is acquired when the tree has initialized
+ var mainTreeApi = null;
+
+ eventsService.on("app.navigationReady", function (e, args) {
+ mainTreeApi = args.treeApi;
+ });
//used to track the current dialog object
var currentDialog = null;
-
- //the main tree event handler, which gets assigned via the setupTreeEvents method
- var mainTreeEventHandler = null;
+
//tracks the user profile dialog
var userDialog = null;
@@ -165,100 +169,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
appState.setGlobalState("showTray", false);
},
- /**
- Called to assign the main tree event handler - this is called by the navigation controller.
- TODO: Potentially another dev could call this which would kind of mung the whole app so potentially there's a better way.
- */
- setupTreeEvents: function(treeEventHandler) {
- mainTreeEventHandler = treeEventHandler;
-
- //when a tree is loaded into a section, we need to put it into appState
- mainTreeEventHandler.bind("treeLoaded", function(ev, args) {
- appState.setTreeState("currentRootNode", args.tree);
- });
-
- //when a tree node is synced this event will fire, this allows us to set the currentNode
- mainTreeEventHandler.bind("treeSynced", function (ev, args) {
-
- if (args.activate === undefined || args.activate === true) {
- //set the current selected node
- appState.setTreeState("selectedNode", args.node);
- //when a node is activated, this is the same as clicking it and we need to set the
- //current menu item to be this node as well.
- appState.setMenuState("currentNode", args.node);
- }
- });
-
- //this reacts to the options item in the tree
- mainTreeEventHandler.bind("treeOptionsClick", function(ev, args) {
- ev.stopPropagation();
- ev.preventDefault();
-
- //Set the current action node (this is not the same as the current selected node!)
- appState.setMenuState("currentNode", args.node);
-
- if (args.event && args.event.altKey) {
- args.skipDefault = true;
- }
-
- service.showMenu(ev, args);
- });
-
- mainTreeEventHandler.bind("treeNodeAltSelect", function(ev, args) {
- ev.stopPropagation();
- ev.preventDefault();
-
- args.skipDefault = true;
- service.showMenu(ev, args);
- });
-
- //this reacts to tree items themselves being clicked
- //the tree directive should not contain any handling, simply just bubble events
- mainTreeEventHandler.bind("treeNodeSelect", function (ev, args) {
- var n = args.node;
- ev.stopPropagation();
- ev.preventDefault();
-
- if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") {
- //this is a legacy tree node!
- var jsPrefix = "javascript:";
- var js;
- if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) {
- js = n.metaData["jsClickCallback"].substr(jsPrefix.length);
- }
- else {
- js = n.metaData["jsClickCallback"];
- }
- try {
- var func = eval(js);
- //this is normally not necessary since the eval above should execute the method and will return nothing.
- if (func != null && (typeof func === "function")) {
- func.call();
- }
- }
- catch(ex) {
- $log.error("Error evaluating js callback from legacy tree node: " + ex);
- }
- }
- else if (n.routePath) {
- //add action to the history service
- historyService.add({ name: n.name, link: n.routePath, icon: n.icon });
-
- //put this node into the tree state
- appState.setTreeState("selectedNode", args.node);
- //when a node is clicked we also need to set the active menu node to this node
- appState.setMenuState("currentNode", args.node);
-
- //not legacy, lets just set the route value and clear the query string if there is one.
- $location.path(n.routePath).search("");
- }
- else if (args.element.section) {
- $location.path(args.element.section).search("");
- }
-
- service.hideNavigation();
- });
- },
/**
* @ngdoc method
* @name umbraco.services.navigationService#syncTree
@@ -288,12 +198,9 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
throw "args.tree cannot be null";
}
- if (mainTreeEventHandler) {
-
- if (mainTreeEventHandler.syncTree) {
- //returns a promise,
- return mainTreeEventHandler.syncTree(args);
- }
+ if (mainTreeApi) {
+ //returns a promise,
+ return mainTreeApi.syncTree(args);
}
//couldn't sync
@@ -305,33 +212,27 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
have to set an active tree and then sync, the new API does this in one method by using syncTree
*/
_syncPath: function(path, forceReload) {
- if (mainTreeEventHandler) {
- mainTreeEventHandler.syncTree({ path: path, forceReload: forceReload });
+ if (mainTreeApi) {
+ mainTreeApi.syncTree({ path: path, forceReload: forceReload });
}
},
-
- //TODO: This should return a promise
+
reloadNode: function(node) {
- if (mainTreeEventHandler) {
- mainTreeEventHandler.reloadNode(node);
+ if (mainTreeApi) {
+ return mainTreeApi.reloadNode(node);
+ }
+ else {
+ return $q.reject();
}
},
-
- //TODO: This should return a promise
+
reloadSection: function(sectionAlias) {
- if (mainTreeEventHandler) {
- mainTreeEventHandler.clearCache({ section: sectionAlias });
- mainTreeEventHandler.load(sectionAlias);
+ if (mainTreeApi) {
+ mainTreeApi.clearCache({ section: sectionAlias });
+ return mainTreeApi.load(sectionAlias);
}
- },
-
- /**
- Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
- have to set an active tree and then sync, the new API does this in one method by using syncTreePath
- */
- _setActiveTreeType: function (treeAlias, loadChildren) {
- if (mainTreeEventHandler) {
- mainTreeEventHandler._setActiveTreeType(treeAlias, loadChildren);
+ else {
+ return $q.reject();
}
},
@@ -364,7 +265,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
*
* @param {Event} event the click event triggering the method, passed from the DOM element
*/
- showMenu: function(event, args) {
+ showMenu: function(args) {
var deferred = $q.defer();
var self = this;
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 888a067a66..3528eaadd0 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
@@ -263,8 +298,14 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
if (args.node.children && args.node.children.length > 0) {
args.node.expanded = true;
args.node.hasChildren = true;
- }
- return data;
+ }
+
+ //Since we've removed the children & reloaded them, we need to refresh the UI now because the tree node UI doesn't operate on normal angular $watch since that will be pretty slow
+ if (angular.isFunction(args.node.updateNodeData)) {
+ args.node.updateNodeData(args.node);
+ }
+
+ return $q.when(data);
}, function(reason) {
@@ -277,7 +318,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
//tell notications about the error
notificationsService.error(reason);
- return reason;
+ return $q.reject(reason);
});
},
@@ -475,8 +516,6 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
*/
getTree: function (args) {
- var deferred = $q.defer();
-
//set defaults
if (!args) {
args = { section: 'content', cacheKey: null };
@@ -489,12 +528,11 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
//return the cache if it exists
if (cacheKey && treeCache[cacheKey] !== undefined) {
- deferred.resolve(treeCache[cacheKey]);
- return deferred.promise;
+ return $q.when(treeCache[cacheKey]);
}
var self = this;
- treeResource.loadApplication(args)
+ return treeResource.loadApplication(args)
.then(function(data) {
//this will be called once the tree app data has loaded
var result = {
@@ -507,16 +545,14 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
//cache this result if a cache key is specified - generally a cache key should ONLY
// be specified for application trees, dialog trees should not be cached.
- if (cacheKey) {
+ if (cacheKey) {
treeCache[cacheKey] = result;
- deferred.resolve(treeCache[cacheKey]);
+ return $q.when(treeCache[cacheKey]);
}
//return un-cached
- deferred.resolve(result);
+ return $q.when(result);
});
-
- return deferred.promise;
},
/**
@@ -579,7 +615,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
.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;
+ return $q.when(data);
});
},
@@ -604,12 +640,10 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
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) {
+ return 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) {
@@ -632,17 +666,15 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
//set the node loading
node.parent().children[index].loading = false;
- //return
- deferred.resolve(node.parent().children[index]);
+ //return
+ return $q.when(node.parent().children[index]);
}
else {
- deferred.reject();
+ return $q.reject();
}
}, function() {
- deferred.reject();
+ return $q.reject();
});
-
- return deferred.promise;
},
/**
@@ -696,8 +728,6 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
args.path.push("-1");
}
- 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) {
@@ -711,8 +741,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
if (String(args.path[currPathIndex]).toLowerCase() === String(args.node.id).toLowerCase()) {
if (args.path.length === 1) {
//return the root
- deferred.resolve(root);
- return deferred.promise;
+ return $q.when(root);
}
else {
//move to the next path part and continue
@@ -732,16 +761,12 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
if (args.path.length === (currPathIndex + 1)) {
//woot! synced the node
if (!args.forceReload) {
- deferred.resolve(child);
+ return $q.when(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();
- });
+ return self.reloadNode(child);
}
}
else {
@@ -749,46 +774,44 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
currPathIndex++;
node = child;
//recurse
- doSync();
+ return doSync();
}
}
else {
//couldn't find it in the
- self.loadNodeChildren({ node: node, section: node.section }).then(function () {
+ return self.loadNodeChildren({ node: node, section: node.section }).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);
+ return $q.when(found);
}
else {
//now we need to recurse with the updated node/currPathIndex
currPathIndex++;
node = found;
//recurse
- doSync();
+ return doSync();
}
}
else {
//fail!
- deferred.reject();
+ return $q.reject();
}
}, function () {
//fail!
- deferred.reject();
+ return $q.reject();
});
}
};
//start
- doSync();
-
- return deferred.promise;
+ return doSync();
}
};
}
-angular.module('umbraco.services').factory('treeService', treeService);
\ No newline at end of file
+angular.module('umbraco.services').factory('treeService', treeService);
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
index 80565c23e3..316a75117b 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js
@@ -247,10 +247,9 @@ angular.module('umbraco.services')
/** Returns the current user object in a promise */
getCurrentUser: function (args) {
- var deferred = $q.defer();
-
+
if (!currentUser) {
- authResource.getCurrentUser()
+ return authResource.getCurrentUser()
.then(function (data) {
var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" };
@@ -261,19 +260,17 @@ angular.module('umbraco.services')
}
setCurrentUser(data);
-
- deferred.resolve(currentUser);
+
+ return $q.when(currentUser);
}, function () {
- //it failed, so they are not logged in
- deferred.reject();
+ //it failed, so they are not logged in
+ return $q.reject(currentUser);
});
}
else {
- deferred.resolve(currentUser);
+ return $q.when(currentUser);
}
-
- return deferred.promise;
},
/** Loads the Moment.js Locale for the current user. */
@@ -295,11 +292,7 @@ angular.module('umbraco.services')
return assetsService.load(localeUrls, $rootScope);
}
else {
- //return a noop promise
- var deferred = $q.defer();
- var promise = deferred.promise;
- deferred.resolve(true);
- return promise;
+ $q.when(true);
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
index 377424f3d4..b4a1f284d5 100644
--- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js
@@ -14,6 +14,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
$scope.authenticated = null;
$scope.touchDevice = appState.getGlobalState("touchDevice");
$scope.overlay = {};
+ $scope.navReady = false;
$scope.removeNotification = function (index) {
@@ -60,6 +61,10 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $
});
}));
+ evts.push(eventsService.on('app.navigationReady', function () {
+ $scope.navReady = true;
+ }));
+
//when the app is ready/user is logged in, setup the data
evts.push(eventsService.on("app.ready", function (evt, data) {
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 6330f5a30a..b1810ca683 100644
--- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js
@@ -9,18 +9,113 @@
*
* @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) {
- //TODO: Need to think about this and an nicer way to acheive what this is doing.
- //the tree event handler i used to subscribe to the main tree click events
- $scope.treeEventHandler = $({});
- navigationService.setupTreeEvents($scope.treeEventHandler);
+ $scope.treeApi = {};
- //Put the navigation service on this scope so we can use it's methods/properties in the view.
+ //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);
+ });
+
+ //when a tree node is synced this event will fire, this allows us to set the currentNode
+ $scope.treeApi.callbacks.treeSynced(function (args) {
+
+ if (args.activate === undefined || args.activate === true) {
+ //set the current selected node
+ appState.setTreeState("selectedNode", args.node);
+ //when a node is activated, this is the same as clicking it and we need to set the
+ //current menu item to be this node as well.
+ appState.setMenuState("currentNode", args.node);
+ }
+ });
+
+ //this reacts to the options item in the tree
+ $scope.treeApi.callbacks.treeOptionsClick(function (args) {
+ args.event.stopPropagation();
+ args.event.preventDefault();
+
+ //Set the current action node (this is not the same as the current selected node!)
+ appState.setMenuState("currentNode", args.node);
+
+ if (args.event && args.event.altKey) {
+ args.skipDefault = true;
+ }
+
+ navigationService.showMenu(args);
+ });
+
+ $scope.treeApi.callbacks.treeNodeAltSelect(function (args) {
+ args.event.stopPropagation();
+ args.event.preventDefault();
+
+ args.skipDefault = true;
+ navigationService.showMenu(args);
+ });
+
+ //this reacts to tree items themselves being clicked
+ //the tree directive should not contain any handling, simply just bubble events
+ $scope.treeApi.callbacks.treeNodeSelect(function (args) {
+ var n = args.node;
+ args.event.stopPropagation();
+ args.event.preventDefault();
+
+ if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") {
+ //this is a legacy tree node!
+ var jsPrefix = "javascript:";
+ var js;
+ if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) {
+ js = n.metaData["jsClickCallback"].substr(jsPrefix.length);
+ }
+ else {
+ js = n.metaData["jsClickCallback"];
+ }
+ try {
+ var func = eval(js);
+ //this is normally not necessary since the eval above should execute the method and will return nothing.
+ if (func != null && (typeof func === "function")) {
+ func.call();
+ }
+ }
+ catch (ex) {
+ $log.error("Error evaluating js callback from legacy tree node: " + ex);
+ }
+ }
+ else if (n.routePath) {
+ //add action to the history service
+ historyService.add({ name: n.name, link: n.routePath, icon: n.icon });
+
+ //put this node into the tree state
+ appState.setTreeState("selectedNode", args.node);
+ //when a node is clicked we also need to set the active menu node to this node
+ appState.setMenuState("currentNode", args.node);
+
+ //not legacy, lets just set the route value and clear the query string if there is one.
+ $location.path(n.routePath).search("");
+ }
+ else if (n.section) {
+ $location.path(n.section).search("");
+ }
+
+ navigationService.hideNavigation();
+ });
+
+ //once this is wired up, the nav is ready
+ eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi});
+ }
+
+ //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;
@@ -37,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 () {
@@ -137,32 +236,86 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams,
languageResource.getAll().then(function(languages) {
$scope.languages = languages;
- // select the default language
- $scope.languages.forEach(function(language) {
- if(language.isDefault) {
- $scope.selectLanguage(language);
+ // make the default language selected
+ $scope.languages.forEach(function (language) {
+ if (language.isDefault) {
+ $scope.selectedLanguage = language;
}
});
-
});
}));
- $scope.selectLanguage = function(language, languages) {
+ /**
+ * 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) {
$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
+
+ //execute after next digest because the internal watch on the customtreeparams needs to be bound now that we've changed it
+ $timeout(function () {
+ //reload the tree with it's updated querystring args
+ $scope.treeApi.load($scope.currentSection).then(function () {
+
+ //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 }));
+ }
+ //TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding
+ //for (var i = 0; i < expandedPaths.length; i++) {
+ // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true }));
+ //}
+ //execute them sequentially
+ angularHelper.executeSequentialPromises(promises);
+ });
+ });
+
};
//this reacts to the options item in the tree
- //todo, migrate to nav service
+ //TODO: migrate to nav service
+ //TODO: is this used?
$scope.searchShowMenu = function (ev, args) {
//always skip default
args.skipDefault = true;
- navigationService.showMenu(ev, args);
+ navigationService.showMenu(args);
};
- //todo, migrate to nav service
+ //TODO: migrate to nav service
+ //TODO: is this used?
$scope.searchHide = function () {
navigationService.hideSearch();
};
diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js
index 32462826fe..7fb35a4cd0 100644
--- a/src/Umbraco.Web.UI.Client/src/init.js
+++ b/src/Umbraco.Web.UI.Client/src/init.js
@@ -1,31 +1,32 @@
/** Executed when the application starts, binds to events and set global state */
-app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource',
- function (userService, $log, $rootScope, $location, queryStrings, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) {
-
+app.run(['userService', '$q', '$log', '$rootScope', '$location', 'queryStrings', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache', 'localStorageService', 'tourService', 'dashboardResource',
+ function (userService, $q, $log, $rootScope, $location, queryStrings, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache, localStorageService, tourService, dashboardResource) {
+
//This sets the default jquery ajax headers to include our csrf token, we
// need to user the beforeSend method because our token changes per user/login so
// it cannot be static
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader("X-UMB-XSRF-TOKEN", $cookies["UMB-XSRF-TOKEN"]);
- if (queryStrings.getParams().umbDebug === "true" || queryStrings.getParams().umbdebug === "true") {
- xhr.setRequestHeader("X-UMB-DEBUG", "true");
- }
+ if (queryStrings.getParams().umbDebug === "true" || queryStrings.getParams().umbdebug === "true") {
+ xhr.setRequestHeader("X-UMB-DEBUG", "true");
+ }
}
});
/** Listens for authentication and checks if our required assets are loaded, if/once they are we'll broadcast a ready event */
- eventsService.on("app.authenticated", function(evt, data) {
-
- assetsService._loadInitAssets().then(function() {
+ eventsService.on("app.authenticated", function (evt, data) {
- // Loads the user's locale settings for Moment.
- userService.loadMomentLocaleForCurrentUser().then(function() {
+ assetsService._loadInitAssets().then(function() {
+ $q.all([
+ userService.loadMomentLocaleForCurrentUser(),
+ tourService.registerAllTours()
+ ]).then(function () {
//Register all of the tours on the server
tourService.registerAllTours().then(function () {
appReady(data);
-
+
// Auto start intro tour
tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) {
// start intro tour if it hasn't been completed or disabled
@@ -34,7 +35,8 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
}
});
- }, function(){
+ }, function () {
+ appAuthenticated = true;
appReady(data);
});
});
@@ -50,7 +52,7 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
}
/** execute code on each successful route */
- $rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
+ $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
var deployConfig = Umbraco.Sys.ServerVariables.deploy;
var deployEnv, deployEnvTitle;
@@ -59,7 +61,7 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
deployEnvTitle = "(" + deployEnv + ") ";
}
- if(current.params.section) {
+ if (current.params.section) {
//Uppercase the current section, content, media, settings, developer, forms
var currentSection = current.params.section.charAt(0).toUpperCase() + current.params.section.slice(1);
@@ -67,18 +69,18 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
var baseTitle = currentSection + " - " + $location.$$host;
//Check deploy for Global Umbraco.Sys obj workspace
- if(deployEnv){
+ if (deployEnv) {
$rootScope.locationTitle = deployEnvTitle + baseTitle;
}
else {
$rootScope.locationTitle = baseTitle;
}
-
+
}
else {
- if(deployEnv) {
- $rootScope.locationTitle = deployEnvTitle + "Umbraco - " + $location.$$host;
+ if (deployEnv) {
+ $rootScope.locationTitle = deployEnvTitle + "Umbraco - " + $location.$$host;
}
$rootScope.locationTitle = "Umbraco - " + $location.$$host;
@@ -95,7 +97,7 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
/** When the route change is rejected - based on checkAuth - we'll prevent the rejected route from executing including
wiring up it's controller, etc... and then redirect to the rejected URL. */
- $rootScope.$on('$routeChangeError', function(event, current, previous, rejection) {
+ $rootScope.$on('$routeChangeError', function (event, current, previous, rejection) {
event.preventDefault();
var returnPath = null;
@@ -111,13 +113,12 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi
});
-
/* this will initialize the navigation service once the application has started */
navigationService.init();
//check for touch device, add to global appState
//var touchDevice = ("ontouchstart" in window || window.touch || window.navigator.msMaxTouchPoints === 5 || window.DocumentTouch && document instanceof DocumentTouch);
- var touchDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|touch/i.test(navigator.userAgent.toLowerCase());
+ var touchDevice = /android|webos|iphone|ipad|ipod|blackberry|iemobile|touch/i.test(navigator.userAgent.toLowerCase());
appState.setGlobalState("touchDevice", touchDevice);
}]);
diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js
index 3fc4d3f78e..5f660cf212 100644
--- a/src/Umbraco.Web.UI.Client/src/routes.js
+++ b/src/Umbraco.Web.UI.Client/src/routes.js
@@ -1,54 +1,56 @@
app.config(function ($routeProvider) {
-
- /** This checks if the user is authenticated for a route and what the isRequired is set to.
- Depending on whether isRequired = true, it first check if the user is authenticated and will resolve successfully
+
+ /**
+ * This determines if the route can continue depending on authentication and initialization requirements
+ * @param {boolean} authRequired If true, it first checks if the user is authenticated and will resolve successfully
otherwise the route will fail and the $routeChangeError event will execute, in that handler we will redirect to the rejected
- path that is resolved from this method and prevent default (prevent the route from executing) */
- var canRoute = function(isRequired) {
+ path that is resolved from this method and prevent default (prevent the route from executing)
+ * @param {boolean} navRequired if true, the route can only continue once the main navigation is ready
+ * @returns {promise}
+ */
+ var canRoute = function(authRequired) {
return {
/** Checks that the user is authenticated, then ensures that are requires assets are loaded */
isAuthenticatedAndReady: function ($q, userService, $route, assetsService, appState) {
- var deferred = $q.defer();
-
+
//don't need to check if we've redirected to login and we've already checked auth
if (!$route.current.params.section
&& ($route.current.params.check === false || $route.current.params.check === "false")) {
- deferred.resolve(true);
- return deferred.promise;
+ return $q.when(true);
}
- userService.isAuthenticated()
+ return userService.isAuthenticated()
.then(function () {
- assetsService._loadInitAssets().then(function() {
+ return assetsService._loadInitAssets().then(function () {
//This could be the first time has loaded after the user has logged in, in this case
// we need to broadcast the authenticated event - this will be handled by the startup (init)
// handler to set/broadcast the ready state
var broadcast = appState.getGlobalState("isReady") !== true;
- userService.getCurrentUser({ broadcastEvent: broadcast }).then(function (user) {
+ return userService.getCurrentUser({ broadcastEvent: broadcast }).then(function (user) {
//is auth, check if we allow or reject
- if (isRequired) {
-
+ if (authRequired) {
+
//This checks the current section and will force a redirect to 'content' as the default
if ($route.current.params.section.toLowerCase() === "default" || $route.current.params.section.toLowerCase() === "umbraco" || $route.current.params.section === "") {
$route.current.params.section = "content";
- }
+ }
// U4-5430, Benjamin Howarth
// We need to change the current route params if the user only has access to a single section
// To do this we need to grab the current user's allowed sections, then reject the promise with the correct path.
if (user.allowedSections.indexOf($route.current.params.section) > -1) {
//this will resolve successfully so the route will continue
- deferred.resolve(true);
+ return $q.when(true);
} else {
- deferred.reject({ path: "/" + user.allowedSections[0] });
+ return $q.reject({ path: "/" + user.allowedSections[0] });
}
}
else {
- deferred.reject({ path: "/" });
+ return $q.reject({ path: "/" });
}
});
@@ -56,17 +58,17 @@ app.config(function ($routeProvider) {
}, function () {
//not auth, check if we allow or reject
- if (isRequired) {
+ if (authRequired) {
//the check=false is checked above so that we don't have to make another http call to check
//if they are logged in since we already know they are not.
- deferred.reject({ path: "/login/false" });
+ return $q.reject({ path: "/login/false" });
}
else {
//this will resolve successfully so the route will continue
- deferred.resolve(true);
+ return $q.when(true);
}
});
- return deferred.promise;
+
}
};
};
@@ -75,15 +77,13 @@ app.config(function ($routeProvider) {
var doLogout = function() {
return {
isLoggedOut: function ($q, userService) {
- var deferred = $q.defer();
- userService.logout().then(function () {
+ return userService.logout().then(function () {
//success so continue
- deferred.resolve(true);
+ return $q.when(true);
}, function() {
//logout failed somehow ? we'll reject with the login page i suppose
- deferred.reject({ path: "/login/false" });
+ return $q.reject({ path: "/login/false" });
});
- return deferred.promise;
}
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js
index e76db90f47..5055f088c7 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js
@@ -8,7 +8,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
searchText = value + "...";
});
- $scope.dialogTreeEventHandler = $({});
+ $scope.dialogTreeApi = {};
$scope.target = {};
$scope.searchInfo = {
searchFromId: null,
@@ -30,7 +30,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
entityResource.getPath(id, "Document").then(function (path) {
$scope.target.path = path;
//now sync the tree to this path
- $scope.dialogTreeEventHandler.syncTree({ path: $scope.target.path, tree: "content" });
+ $scope.dialogTreeApi.syncTree({ path: $scope.target.path, tree: "content" });
});
}
@@ -40,7 +40,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
}
}
- function nodeSelectHandler(ev, args) {
+ function nodeSelectHandler(args) {
args.event.preventDefault();
args.event.stopPropagation();
@@ -80,7 +80,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
}
}
- function nodeExpandedHandler(ev, args) {
+ function nodeExpandedHandler(args) {
if (angular.isArray(args.children)) {
//iterate children
@@ -138,12 +138,10 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
-
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- });
- });
\ No newline at end of file
+
+ $scope.onTreeInit = function () {
+ $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
+ }
+
+ });
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html
index 442357edcc..5d1bbd8f80 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html
@@ -46,7 +46,8 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.controller.js
index 93de97a8ea..8962bf91a3 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.controller.js
@@ -2,7 +2,7 @@
angular.module("umbraco").controller("Umbraco.Dialogs.MemberGroupPickerController",
function($scope, eventsService, entityResource, searchService, $log) {
var dialogOptions = $scope.dialogOptions;
- $scope.dialogTreeEventHandler = $({});
+ $scope.dialogTreeApi = {};
$scope.multiPicker = dialogOptions.multiPicker;
/** Method used for selecting a node */
@@ -16,7 +16,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.MemberGroupPickerControlle
}
}
- function nodeSelectHandler(ev, args) {
+ function nodeSelectHandler(args) {
args.event.preventDefault();
args.event.stopPropagation();
@@ -29,10 +29,9 @@ angular.module("umbraco").controller("Umbraco.Dialogs.MemberGroupPickerControlle
//toggle checked state
args.node.selected = args.node.selected === true ? false : true;
}
+
+ function onTreeInit() {
+ $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ }
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- });
- });
\ No newline at end of file
+ });
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html
index e2ff9790d5..23b8bb93e0 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/membergrouppicker.html
@@ -7,8 +7,9 @@
treealias="memberGroups"
hideheader="true"
hideoptions="true"
- isdialog="true"
- eventhandler="dialogTreeEventHandler"
+ isdialog="true"
+ on-init="onTreeInit()"
+ api="dialogTreeApi"
enablecheckboxes="{{multiPicker}}">
@@ -31,4 +32,4 @@
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js
index 740791abf9..e6d0da2b6f 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js
@@ -4,7 +4,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController",
var tree = null;
var dialogOptions = $scope.dialogOptions;
- $scope.dialogTreeEventHandler = $({});
+ $scope.dialogTreeApi = {};
$scope.section = dialogOptions.section;
$scope.treeAlias = dialogOptions.treeAlias;
$scope.multiPicker = dialogOptions.multiPicker;
@@ -138,7 +138,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController",
}
//gets the tree object when it loads
- function treeLoadedHandler(ev, args) {
+ function treeLoadedHandler(args) {
tree = args.tree;
}
@@ -418,14 +418,11 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController",
$scope.searchInfo.showSearch = true;
};
+
+ $scope.onTreeInit = function () {
+ $scope.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
+ $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
+ $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ }
- $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- });
- });
\ No newline at end of file
+ });
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html
index 3d39817680..9d17148042 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.html
@@ -29,7 +29,8 @@
hideoptions="true"
isdialog="true"
customtreeparams="{{customTreeParams}}"
- eventhandler="dialogTreeEventHandler"
+ api="dialogTreeApi"
+ on-init="onTreeInit()"
enablelistviewsearch="true"
enablecheckboxes="{{multiPicker}}">
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.controller.js
index df6b51bea0..47e078d33c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.controller.js
@@ -22,7 +22,7 @@
});
$scope.model.relateToOriginal = true;
- $scope.dialogTreeEventHandler = $({});
+ $scope.dialogTreeApi = {};
vm.searchInfo = {
searchFromId: null,
@@ -35,7 +35,7 @@
// get entity type based on the section
$scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
- function nodeSelectHandler(ev, args) {
+ function nodeSelectHandler(args) {
if(args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
@@ -52,7 +52,7 @@
$scope.model.target.selected = true;
}
- function nodeExpandedHandler(ev, args) {
+ function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
@@ -78,13 +78,11 @@
vm.searchInfo.showSearch = true;
}
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.onTreeInit = function () {
+ $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
+ }
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- });
// Mini list view
$scope.selectListViewNode = function (node) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html
index aeafaec04b..16d00c8689 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/copy/copy.html
@@ -26,7 +26,8 @@
hideheader="false"
hideoptions="true"
isdialog="true"
- eventhandler="dialogTreeEventHandler"
+ api="dialogTreeApi"
+ on-init="onTreeInit()"
enablelistviewexpand="true"
enablecheckboxes="true">
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js
index 91c74311b3..653403a0f5 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js
@@ -12,7 +12,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
$scope.model.title = localizationService.localize("defaultdialogs_selectLink");
}
- $scope.dialogTreeEventHandler = $({});
+ $scope.dialogTreeApi = {};
$scope.model.target = {};
$scope.searchInfo = {
searchFromId: null,
@@ -38,7 +38,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
entityResource.getPath(id, "Document").then(function (path) {
$scope.model.target.path = path;
//now sync the tree to this path
- $scope.dialogTreeEventHandler.syncTree({ path: $scope.model.target.path, tree: "content" });
+ $scope.dialogTreeApi.syncTree({ path: $scope.model.target.path, tree: "content" });
});
}
@@ -48,7 +48,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
}
}
- function nodeSelectHandler(ev, args) {
+ function nodeSelectHandler(args) {
if(args && args.event) {
args.event.preventDefault();
@@ -82,7 +82,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
}
}
- function nodeExpandedHandler(ev, args) {
+ function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
@@ -131,14 +131,11 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
$scope.searchInfo.showSearch = true;
};
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- });
-
+ $scope.onTreeInit = function () {
+ $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
+ }
+
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html
index e1b13206df..a59207c46a 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html
@@ -52,7 +52,8 @@
section="content"
hideheader="true"
hideoptions="true"
- eventhandler="dialogTreeEventHandler"
+ api="dialogTreeApi"
+ on-init="onTreeInit()"
enablelistviewexpand="true"
isdialog="true"
enablecheckboxes="true">
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js
index e823b2cc44..d60f3c32f5 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.controller.js
@@ -6,7 +6,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerControll
$scope.model.title = localizationService.localize("defaultdialogs_selectMemberGroup");
}
- $scope.dialogTreeEventHandler = $({});
+ $scope.dialogTreeApi = {};
$scope.multiPicker = $scope.model.multiPicker;
function activate() {
@@ -39,7 +39,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerControll
}
}
- function nodeSelectHandler(ev, args) {
+ function nodeSelectHandler(args) {
args.event.preventDefault();
args.event.stopPropagation();
@@ -52,13 +52,11 @@ angular.module("umbraco").controller("Umbraco.Overlays.MemberGroupPickerControll
//toggle checked state
args.node.selected = args.node.selected === true ? false : true;
}
-
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- });
-
+
+ $scope.onTreeInit = function () {
+ $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ };
+
activate();
});
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html
index 22fdeaf8be..a416abf64b 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/membergrouppicker/membergrouppicker.html
@@ -5,7 +5,8 @@
hideheader="true"
hideoptions="true"
isdialog="true"
- eventhandler="dialogTreeEventHandler"
+ api="dialogTreeApi"
+ on-init="onTreeInit()"
enablecheckboxes="{{model.multiPicker}}">
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.controller.js
index 78b7c7be34..adf616a603 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.controller.js
@@ -22,7 +22,7 @@
}
$scope.model.relateToOriginal = true;
- $scope.dialogTreeEventHandler = $({});
+ $scope.dialogTreeApi = {};
vm.searchInfo = {
searchFromId: null,
@@ -35,7 +35,7 @@
// get entity type based on the section
$scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
- function nodeSelectHandler(ev, args) {
+ function nodeSelectHandler(args) {
if(args && args.event) {
args.event.preventDefault();
@@ -54,7 +54,7 @@
}
- function nodeExpandedHandler(ev, args) {
+ function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
@@ -80,13 +80,10 @@
vm.searchInfo.showSearch = true;
}
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- });
+ $scope.onTreeInit = function () {
+ $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ $scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
+ }
// Mini list view
$scope.selectListViewNode = function (node) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.html
index 34130c31a8..30afe56d4b 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/move/move.html
@@ -25,7 +25,8 @@
hideheader="false"
hideoptions="true"
isdialog="true"
- eventhandler="dialogTreeEventHandler"
+ api="dialogTreeApi"
+ on-init="onTreeInit()"
enablelistviewexpand="true"
enablecheckboxes="true">
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 2faf4ebaa5..dec40440f2 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
@@ -23,10 +23,13 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
// Search and listviews is only working for content, media and member section
var searchableSections = ["content", "media", "member"];
+ // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths
+ var expandedPaths = [];
var vm = this;
- vm.treeReady = false;
- vm.dialogTreeEventHandler = $({});
+ vm.treeReady = false;
+ vm.dialogTreeApi = {};
+ vm.initDialogTree = initDialogTree;
vm.section = $scope.model.section;
vm.treeAlias = $scope.model.treeAlias;
vm.multiPicker = $scope.model.multiPicker;
@@ -61,11 +64,18 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
vm.closeMiniListView = closeMiniListView;
vm.selectListViewNode = selectListViewNode;
+ function initDialogTree() {
+ vm.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
+ //TODO: Also deal with unexpanding!!
+ vm.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
+ vm.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
+ }
+
/**
* Performs the initialization of this component
*/
- function onInit () {
-
+ function onInit () {
+
// load languages
languageResource.getAll().then(function (languages) {
vm.languages = languages;
@@ -97,14 +107,8 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
$scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
}
}
-
- //var searchText = "Search...";
- //localizationService.localize("general_search").then(function (value) {
- // searchText = value + "...";
- //});
- //min / max values
- //TODO: Where are these used?
+ //TODO: Seems odd this logic is here, i don't think it needs to be and should just exist on the property editor using this
if ($scope.model.minNumber) {
$scope.model.minNumber = parseInt($scope.model.minNumber, 10);
}
@@ -188,14 +192,35 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
vm.selectedLanguage = language;
// close the language selector
vm.languageSelectorIsOpen = false;
- initTree();
+
+ initTree(); //this will reset the tree params and the tree directive will pick up the changes in a $watch
+
+ //execute after next digest because the internal watch on the customtreeparams needs to be bound now that we've changed it
+ $timeout(function () {
+ //reload the tree with it's updated querystring args
+ vm.dialogTreeApi.load(vm.section).then(function () {
+
+ //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 }));
+ }
+ //execute them sequentially
+ angularHelper.executeSequentialPromises(promises);
+ });
+ });
};
function toggleLanguageSelector() {
vm.languageSelectorIsOpen = !vm.languageSelectorIsOpen;
};
-
- function nodeExpandedHandler(ev, args) {
+
+ function nodeExpandedHandler(args) {
+
+ //store the reference to the expanded node path
+ if (args.node) {
+ treeService._trackExpandedPaths(args.node, expandedPaths);
+ }
// open mini list view for list views
if (args.node.metaData.isContainer) {
@@ -225,7 +250,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
}
//gets the tree object when it loads
- function treeLoadedHandler(ev, args) {
+ function treeLoadedHandler(args) {
//args.tree contains children (args.tree.root.children)
vm.hasItems = args.tree.root.children.length > 0;
@@ -233,7 +258,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
}
//wires up selection
- function nodeSelectHandler(ev, args) {
+ function nodeSelectHandler(args) {
args.event.preventDefault();
args.event.stopPropagation();
@@ -578,17 +603,6 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
function closeMiniListView() {
vm.miniListView = undefined;
}
-
- vm.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
- vm.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
- vm.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
-
- $scope.$on('$destroy',
- function () {
- vm.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
- vm.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- vm.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- });
//initialize
onInit();
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
index fd198bb0fb..1beffb585e 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
@@ -39,10 +39,11 @@
isdialog="true"
onlyinitialized="{{vm.onlyInitialized}}"
customtreeparams="{{vm.customTreeParams}}"
- eventhandler="vm.dialogTreeEventHandler"
enablelistviewsearch="true"
enablelistviewexpand="true"
- enablecheckboxes="{{vm.multiPicker}}">
+ enablecheckboxes="{{vm.multiPicker}}"
+ on-init="vm.initDialogTree()"
+ api="vm.dialogTreeApi">
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 5eee7cfec9..a470bfe307 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
@@ -16,11 +16,13 @@
-
\n\n";
-
- UmbEditor.Insert(code, '', self._opts.codeEditorElementId);
- }
- });
- },
-
-
- doSubmit: function () {
- /// Submits the data to the server for saving
- var codeVal = UmbClientMgr.contentFrame().UmbEditor.GetCode();
- var self = this;
-
- if (this._opts.editorType == "Template") {
- //saving a template view
-
- $.post(self._opts.restServiceLocation + "SaveTemplate",
- JSON.stringify({
- templateName: this._opts.nameTxtBox.val(),
- templateAlias: this._opts.aliasTxtBox.val(),
- templateContents: codeVal,
- templateId: this._opts.templateId,
- masterTemplateId: this._opts.masterPageDropDown.val()
- }),
- function(e) {
- if (e.success) {
- self.submitSuccess(e);
- } else {
- self.submitFailure(e.message, e.header);
- }
- });
- }
- else {
- //saving a partial view
- var actionName = this._opts.editorType === "PartialViewMacro" ? "SavePartialViewMacro" : "SavePartialView";
-
- $.post(self._opts.restServiceLocation + actionName,
- JSON.stringify({
- filename: this._opts.nameTxtBox.val(),
- oldName: this._opts.originalFileName,
- contents: codeVal
- }),
- function(e) {
- if (e.success) {
- self.submitSuccess(e);
- } else {
- self.submitFailure(e.message, e.header);
- }
- });
- }
- },
-
- submitSuccess: function (args) {
-
- var msg = args.message;
- var header = args.header;
- var path = this._opts.treeSyncPath;
- var pathChanged = false;
- if (args.path) {
- if (path != args.path) {
- pathChanged = true;
- }
- path = args.path;
- }
- if (args.contents) {
- UmbEditor.SetCode(args.contents);
- } else if (!this.IsSimpleEditor) {
- // Restore focuse to text region. SetCode also does this.
- UmbEditor._editor.focus();
- }
-
- UmbClientMgr.mainTree().setActiveTreeType(this._opts.currentTreeType);
-
- if (this._opts.editorType == "Template") {
-
- var alias = args.alias;
- this._opts.aliasTxtBox.val(alias);
-
- top.UmbSpeechBubble.ShowMessage('save', header, msg);
-
- //templates are different because they are ID based, whereas view files are file based without a static id
-
- if (pathChanged) {
- UmbClientMgr.mainTree().moveNode(this._opts.templateId, path);
- this._opts.treeSyncPath = path;
- }
- else {
- UmbClientMgr.mainTree().syncTree(path, true);
- }
-
- }
- else {
- var newFilePath = this._opts.nameTxtBox.val();
-
-
- function trimStart(str, trim) {
- if (str.startsWith(trim)) {
- return str.substring(trim.length);
- }
- return str;
- }
-
- //if the filename changes, we need to redirect since the file name is used in the url
- if (this._opts.originalFileName != newFilePath) {
- var queryParts = trimStart(window.location.search, "?").split('&');
- var notFileParts = [];
- for (var i = 0; i < queryParts.length; i++) {
- if (queryParts[i].substr(0, "file=".length) != "file=") {
- notFileParts.push(queryParts[i]);
- }
- }
- var newLocation = window.location.pathname + "?" + notFileParts.join("&") + "&file=" + newFilePath;
-
- UmbClientMgr.contentFrame(newLocation);
-
- //we need to do this after we navigate otherwise the navigation will wait unti lthe message timeout is done!
- top.UmbSpeechBubble.ShowMessage('save', header, msg);
- }
- else {
-
- top.UmbSpeechBubble.ShowMessage('save', header, msg);
-
- if (args && args.name) {
- this._opts.originalFileName = args.name;
- }
- if (args && args.path) {
- this._opts.treeSyncPath = args.path;
- }
-
- UmbClientMgr.mainTree().syncTree(path, true, null, newFilePath.split("/")[1]);
- }
- }
-
- },
-
- submitFailure: function (err, header) {
- top.UmbSpeechBubble.ShowMessage('error', header, err);
- },
-
- changeMasterPageFile: function ( ) {
- //var editor = document.getElementById(this._opts.sourceEditorId);
- var templateDropDown = this._opts.masterPageDropDown.get(0);
- var templateCode = UmbClientMgr.contentFrame().UmbEditor.GetCode();
- var newValue = templateDropDown.options[templateDropDown.selectedIndex].id;
-
- var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi");
-
- if (newValue != undefined && newValue != "") {
- if (layoutDefRegex.test(templateCode)) {
- // Declaration exists, so just update it
- templateCode = templateCode.replace(layoutDefRegex, "$1\"" + newValue + "\"$3");
- } else {
- // Declaration doesn't exist, so prepend to start of doc
- //TODO: Maybe insert at the cursor position, rather than just at the top of the doc?
- templateCode = "@{\n\tLayout = \"" + newValue + "\";\n}\n" + templateCode;
- }
- } else {
- if (layoutDefRegex.test(templateCode)) {
- // Declaration exists, so just update it
- templateCode = templateCode.replace(layoutDefRegex, "$1null$3");
- }
- }
-
- UmbClientMgr.contentFrame().UmbEditor.SetCode(templateCode);
-
- return false;
- }
- });
-
-
- //Set defaults for jQuery ajax calls.
- $.ajaxSetup({
- dataType: 'json',
- cache: false,
- contentType: 'application/json; charset=utf-8'
- });
-
-})(jQuery);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.css b/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.css
deleted file mode 100644
index 975c68b0c2..0000000000
--- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.css
+++ /dev/null
@@ -1,14 +0,0 @@
-#errorDiv
-{
- margin-bottom: 10px;
-}
-
- #errorDiv a
- {
- float: right;
- }
-
-.propertyItemheader
-{
- width: 200px !important;
-}
diff --git a/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.js b/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.js
deleted file mode 100644
index 67f0259afb..0000000000
--- a/src/Umbraco.Web.UI/umbraco_client/Editors/EditXslt.js
+++ /dev/null
@@ -1,80 +0,0 @@
-Umbraco.Sys.registerNamespace("Umbraco.Editors");
-
-(function ($) {
-
- Umbraco.Editors.EditXslt = base2.Base.extend({
- //private methods/variables
- _opts: null,
-
- // Constructor
- constructor: function(opts) {
- // Merge options with default
- this._opts = $.extend({
-
-
- // Default options go here
- }, opts);
- },
-
- init: function () {
- //setup UI elements
- var self = this;
-
- //bind to the save event
- this._opts.saveButton.click(function (event) {
- event.preventDefault();
- self.doSubmit();
- });
- },
-
- doSubmit: function () {
- var self = this;
-
- var fileName = this._opts.nameTxtBox.val();
- var codeVal = this._opts.editorSourceElement.val();
- //if CodeMirror is not defined, then the code editor is disabled.
- if (typeof (CodeMirror) != "undefined") {
- codeVal = UmbEditor.GetCode();
- }
- umbraco.presentation.webservices.codeEditorSave.SaveXslt(
- fileName, self._opts.originalFileName, codeVal, true,
- function (t) { self.submitSucces(t); },
- function (t) { self.submitFailure(t); });
-
- },
-
- submitSucces: function (t) {
- if (t != 'true') {
- top.UmbSpeechBubble.ShowMessage('error', 'Saving XSLT file failed',"
" + t + "
");
- }
-
- var newFilePath = this._opts.nameTxtBox.val();
-
- //if the filename changes, we need to redirect since the file name is used in the url
- if (this._opts.originalFileName != newFilePath) {
- var newLocation = window.location.pathname + "?" + "&file=" + newFilePath;
-
- UmbClientMgr.contentFrame(newLocation);
-
- //we need to do this after we navigate otherwise the navigation will wait unti lthe message timeout is done!
- top.UmbSpeechBubble.ShowMessage('save', 'XSLT file saved', '');
- }
- else {
-
- top.UmbSpeechBubble.ShowMessage('save', 'XSLT file saved', '');
- UmbClientMgr.mainTree().setActiveTreeType('xslt');
- //we need to pass in the newId parameter so it knows which node to resync after retreival from the server
- UmbClientMgr.mainTree().syncTree("-1,init," + this._opts.originalFileName, true, null, newFilePath);
- //set the original file path to the new one
- this._opts.originalFileName = newFilePath;
- }
-
-
- },
-
- submitFailure: function (t) {
- alert(t);
- top.UmbSpeechBubble.ShowMessage('warning', 'XSLT file could not be saved', '');
- }
- });
-})(jQuery);
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI/umbraco_client/PunyCode/punycode.min.js b/src/Umbraco.Web.UI/umbraco_client/PunyCode/punycode.min.js
deleted file mode 100644
index 5b5c120d4d..0000000000
--- a/src/Umbraco.Web.UI/umbraco_client/PunyCode/punycode.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! https://mths.be/punycode v1.3.2 by @mathias */
-!function (a) { function b(a) { throw RangeError(E[a]) } function c(a, b) { for (var c = a.length, d = []; c--;) d[c] = b(a[c]); return d } function d(a, b) { var d = a.split("@"), e = ""; d.length > 1 && (e = d[0] + "@", a = d[1]), a = a.replace(D, "."); var f = a.split("."), g = c(f, b).join("."); return e + g } function e(a) { for (var b, c, d = [], e = 0, f = a.length; f > e;) b = a.charCodeAt(e++), b >= 55296 && 56319 >= b && f > e ? (c = a.charCodeAt(e++), 56320 == (64512 & c) ? d.push(((1023 & b) << 10) + (1023 & c) + 65536) : (d.push(b), e--)) : d.push(b); return d } function f(a) { return c(a, function (a) { var b = ""; return a > 65535 && (a -= 65536, b += H(a >>> 10 & 1023 | 55296), a = 56320 | 1023 & a), b += H(a) }).join("") } function g(a) { return 10 > a - 48 ? a - 22 : 26 > a - 65 ? a - 65 : 26 > a - 97 ? a - 97 : t } function h(a, b) { return a + 22 + 75 * (26 > a) - ((0 != b) << 5) } function i(a, b, c) { var d = 0; for (a = c ? G(a / x) : a >> 1, a += G(a / b) ; a > F * v >> 1; d += t) a = G(a / F); return G(d + (F + 1) * a / (a + w)) } function j(a) { var c, d, e, h, j, k, l, m, n, o, p = [], q = a.length, r = 0, w = z, x = y; for (d = a.lastIndexOf(A), 0 > d && (d = 0), e = 0; d > e; ++e) a.charCodeAt(e) >= 128 && b("not-basic"), p.push(a.charCodeAt(e)); for (h = d > 0 ? d + 1 : 0; q > h;) { for (j = r, k = 1, l = t; h >= q && b("invalid-input"), m = g(a.charCodeAt(h++)), (m >= t || m > G((s - r) / k)) && b("overflow"), r += m * k, n = x >= l ? u : l >= x + v ? v : l - x, !(n > m) ; l += t) o = t - n, k > G(s / o) && b("overflow"), k *= o; c = p.length + 1, x = i(r - j, c, 0 == j), G(r / c) > s - w && b("overflow"), w += G(r / c), r %= c, p.splice(r++, 0, w) } return f(p) } function k(a) { var c, d, f, g, j, k, l, m, n, o, p, q, r, w, x, B = []; for (a = e(a), q = a.length, c = z, d = 0, j = y, k = 0; q > k; ++k) p = a[k], 128 > p && B.push(H(p)); for (f = g = B.length, g && B.push(A) ; q > f;) { for (l = s, k = 0; q > k; ++k) p = a[k], p >= c && l > p && (l = p); for (r = f + 1, l - c > G((s - d) / r) && b("overflow"), d += (l - c) * r, c = l, k = 0; q > k; ++k) if (p = a[k], c > p && ++d > s && b("overflow"), p == c) { for (m = d, n = t; o = j >= n ? u : n >= j + v ? v : n - j, !(o > m) ; n += t) x = m - o, w = t - o, B.push(H(h(o + x % w, 0))), m = G(x / w); B.push(H(h(m, 0))), j = i(d, r, f == g), d = 0, ++f } ++d, ++c } return B.join("") } function l(a) { return d(a, function (a) { return B.test(a) ? j(a.slice(4).toLowerCase()) : a }) } function m(a) { return d(a, function (a) { return C.test(a) ? "xn--" + k(a) : a }) } var n = "object" == typeof exports && exports && !exports.nodeType && exports, o = "object" == typeof module && module && !module.nodeType && module, p = "object" == typeof global && global; (p.global === p || p.window === p || p.self === p) && (a = p); var q, r, s = 2147483647, t = 36, u = 1, v = 26, w = 38, x = 700, y = 72, z = 128, A = "-", B = /^xn--/, C = /[^\x20-\x7E]/, D = /[\x2E\u3002\uFF0E\uFF61]/g, E = { overflow: "Overflow: input needs wider integers to process", "not-basic": "Illegal input >= 0x80 (not a basic code point)", "invalid-input": "Invalid input" }, F = t - u, G = Math.floor, H = String.fromCharCode; if (q = { version: "1.3.2", ucs2: { decode: e, encode: f }, decode: j, encode: k, toASCII: m, toUnicode: l }, "function" == typeof define && "object" == typeof define.amd && define.amd) define("punycode", function () { return q }); else if (n && o) if (module.exports == n) o.exports = q; else for (r in q) q.hasOwnProperty(r) && (n[r] = q[r]); else a.punycode = q }(this);
\ No newline at end of file
diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs
index 3307c373a6..1988808d23 100644
--- a/src/Umbraco.Web/Editors/BackOfficeController.cs
+++ b/src/Umbraco.Web/Editors/BackOfficeController.cs
@@ -38,17 +38,6 @@ using JArray = Newtonsoft.Json.Linq.JArray;
namespace Umbraco.Web.Editors
{
- public class BackOfficeModel
- {
- public BackOfficeModel(string path, UmbracoFeatures features)
- {
- Path = path;
- Features = features;
- }
-
- public string Path { get; }
- public UmbracoFeatures Features { get; }
- }
///
/// Represents a controller user to render out the default back office view and JS results.
@@ -85,8 +74,8 @@ namespace Umbraco.Web.Editors
public async Task Default()
{
return await RenderDefaultOrProcessExternalLoginAsync(
- () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(GlobalSettings.Path, _features)),
- () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(GlobalSettings.Path, _features)));
+ () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings)),
+ () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings)));
}
[HttpGet]
@@ -159,7 +148,7 @@ namespace Umbraco.Web.Editors
{
return await RenderDefaultOrProcessExternalLoginAsync(
//The default view to render when there is no external login info or errors
- () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml", new BackOfficeModel(GlobalSettings.Path, _features)),
+ () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml", new BackOfficeModel(_features, GlobalSettings)),
//The ActionResult to perform if external login is successful
() => Redirect("/"));
}
diff --git a/src/Umbraco.Web/Editors/BackOfficeModel.cs b/src/Umbraco.Web/Editors/BackOfficeModel.cs
new file mode 100644
index 0000000000..75a388ee80
--- /dev/null
+++ b/src/Umbraco.Web/Editors/BackOfficeModel.cs
@@ -0,0 +1,17 @@
+using Umbraco.Core.Configuration;
+using Umbraco.Web.Features;
+
+namespace Umbraco.Web.Editors
+{
+ public class BackOfficeModel
+ {
+ public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings)
+ {
+ Features = features;
+ GlobalSettings = globalSettings;
+ }
+
+ public UmbracoFeatures Features { get; }
+ public IGlobalSettings GlobalSettings { get; }
+ }
+}
diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs
index 60ef3cf529..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");
@@ -211,7 +212,6 @@ namespace Umbraco.Web.Trees
AddActionNode(item, menu, convert: true);
AddActionNode(item, menu, convert: true);
- AddActionNode(item, menu, true, true);
AddActionNode(item, menu, convert: true);
AddActionNode(item, menu, convert: true);
AddActionNode(item, menu, convert: 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