diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js
index 179d86c5e3..b7020e7aee 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbcontextmenu.directive.js
@@ -1,8 +1,46 @@
angular.module("umbraco.directives")
-.directive('umbContextMenu', function () {
+.directive('umbContextMenu', function ($injector) {
return {
restrict: 'E',
replace: true,
- templateUrl: 'views/directives/umb-contextmenu.html'
+ templateUrl: 'views/directives/umb-contextmenu.html',
+ link: function (scope, element, attrs, ctrl) {
+
+ //adds a handler to the context menu item click, we need to handle this differently
+ //depending on what the menu item is supposed to do.
+ scope.executeMenuItem = function (currentNode, action, currentSection) {
+
+ if (action.metaData && action.metaData["jsAction"] && angular.isString(action.metaData["jsAction"])) {
+
+ //we'll try to get the jsAction from the injector
+ var menuAction = action.metaData["jsAction"].split('.');
+ if (menuAction.length !== 2) {
+ throw "The jsAction assigned to a menu action must have two parts delimited by a '.' ";
+ }
+
+ var service = $injector.get(menuAction[0]);
+ if (!service) {
+ throw "The angular service " + menuAction[0] + " could not be found";
+ }
+
+ var method = service[menuAction[1]];
+
+ if (!method) {
+ throw "The method " + menuAction[1] + " on the angular service " + menuAction[0] + " could not be found";
+ }
+
+ method.apply(this, [{
+ treeNode: currentNode,
+ action: action,
+ section: currentSection
+ }]);
+ }
+ else {
+ //by default we launch the dialog
+ scope.openDialog(currentNode, action, currentSection);
+ }
+ };
+
+ }
};
});
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js
index 950ebf8da5..fcd6777a94 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js
@@ -76,7 +76,7 @@ angular.module("umbraco.directives")
* When changing sections we don't want all of the tree-ndoes to do their 'leave' animations.
*/
scope.animation = function () {
- if (enableDeleteAnimations) {
+ if (enableDeleteAnimations && scope.node.expanded) {
return { leave: 'tree-node-delete-leave' };
}
else {
@@ -94,46 +94,27 @@ angular.module("umbraco.directives")
if (node.expanded) {
enableDeleteAnimations = false;
emitEvent("treeNodeCollapsing", { element: arrow, node: node });
-
- node.expanded = false;
- node.children = [];
+ node.expanded = false;
}
else {
//emit treeNodeExpanding event, if a callback object is set on the tree
emitEvent("treeNodeExpanding", { element: arrow, node: node });
-
- //set element state to loading
- node.loading = true;
-
- //get the children from the tree service
- treeService.getChildren({ node: node, section: scope.section })
- .then(function(data) {
-
- //emit event
- emitEvent("treeNodeLoaded", { element: arrow, node: node, children: data });
-
- //set state to done and expand
- node.loading = false;
- node.children = data;
- node.expanded = true;
-
- //emit expanded event
- emitEvent("treeNodeExpanded", { element: arrow, node: node, children: data });
-
- }, function(reason) {
-
- //in case of error, emit event
- emitEvent("treeNodeLoadError", { element: arrow, node: node, error: reason });
-
- //stop show the loading indicator
- node.loading = false;
-
- //tell notications about the error
- notificationsService.error(reason);
- });
- enableDeleteAnimations = true;
+ if (!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", { element: arrow, node: node, children: data });
+ enableDeleteAnimations = true;
+ });
+ }
+ else {
+ emitEvent("treeNodeExpanded", { element: arrow, node: node, children: node.children });
+ node.expanded = true;
+ enableDeleteAnimations = true;
+ }
}
};
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
new file mode 100644
index 0000000000..7e73c13763
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js
@@ -0,0 +1,11 @@
+
+function umbracoMenuActions($q, treeService) {
+
+ return {
+ refresh: function(args) {
+ treeService.loadNodeChildren({ node: args.treeNode, section: args.section });
+ }
+ };
+}
+
+angular.module('umbraco.services').factory('umbracoMenuActions', umbracoMenuActions);
\ No newline at end of file
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 1b1393979c..43de6f1f46 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
@@ -7,7 +7,7 @@
* @description
* The tree service factory, used internally by the umbTree and umbTreeItem directives
*/
-function treeService($q, treeResource, iconHelper) {
+function treeService($q, treeResource, iconHelper, notificationsService, $rootScope) {
//implement this in local storage
var treeArray = [];
var currentSection = "content";
@@ -28,6 +28,58 @@ function treeService($q, treeResource, iconHelper) {
return {
+ /**
+ * @ngdoc method
+ * @name umbraco.services.treeService#getMenuItemByAlias
+ * @methodOf umbraco.services.treeService
+ * @function
+ *
+ * @description
+ * Clears all node children, gets it's up-to-date children from the server and re-assigns them and then
+ * returns them in a promise.
+ * @param {object} args An arguments object
+ * @param {object} args.node The tree node
+ * @param {object} args.section The current section
+ */
+ loadNodeChildren: function(args) {
+ if (!args) {
+ throw "No args object defined for getChildren";
+ }
+ if (!args.node) {
+ throw "No node defined on args object for getChildren";
+ }
+
+ this.removeChildNodes(args.node);
+ args.node.loading = true;
+
+ return this.getChildren(args)
+ .then(function(data) {
+
+ //set state to done and expand
+ args.node.loading = false;
+ args.node.children = data;
+ args.node.expanded = true;
+ args.node.hasChildren = true;
+
+ return data;
+
+ }, function(reason) {
+
+ //in case of error, emit event
+ $rootScope.$broadcast("treeNodeLoadError", { element: arrow, node: node, error: reason });
+
+ //stop show the loading indicator
+ node.loading = false;
+
+ //tell notications about the error
+ notificationsService.error(reason);
+
+ return reason;
+ });
+
+ },
+
+ /** Removes a given tree node from the tree */
removeNode: function(treeNode) {
if (treeNode.parent == null) {
throw "Cannot remove a node that doesn't have a parent";
@@ -36,7 +88,9 @@ function treeService($q, treeResource, iconHelper) {
treeNode.parent.children.splice(treeNode.parent.children.indexOf(treeNode), 1);
},
+ /** Removes all child nodes from a given tree node */
removeChildNodes : function(treeNode) {
+ treeNode.expanded = false;
treeNode.children = [];
treeNode.hasChildren = false;
},
@@ -59,7 +113,7 @@ function treeService($q, treeResource, iconHelper) {
//check each child of this node
for (var i = 0; i < treeNode.children.length; i++) {
- if (treeNode.children[i].hasChildren) {
+ if (treeNode.children[i].children && angular.isArray(treeNode.children[i].children) && treeNode.children[i].children.length > 0) {
//recurse
found = this.getDescendantNode(treeNode.children[i], id);
if (found) {
@@ -89,14 +143,14 @@ function treeService($q, treeResource, iconHelper) {
return root;
},
- getTree: function (options) {
+ getTree: function (args) {
- if(options === undefined){
- options = {};
+ if (args === undefined) {
+ args = {};
}
- var section = options.section || 'content';
- var cacheKey = options.cachekey || '';
+ var section = args.section || 'content';
+ var cacheKey = args.cachekey || '';
cacheKey += "_" + section;
//return the cache if it exists
@@ -104,7 +158,7 @@ function treeService($q, treeResource, iconHelper) {
return treeArray[cacheKey];
}
- return treeResource.loadApplication(options)
+ return treeResource.loadApplication(args)
.then(function(data) {
//this will be called once the tree app data has loaded
var result = {
@@ -175,28 +229,25 @@ function treeService($q, treeResource, iconHelper) {
});
},
- getChildren: function (options) {
+ /** Gets the children from the server for a given node */
+ getChildren: function (args) {
- if(options === undefined){
- throw "No options object defined for getChildren";
+ if (!args) {
+ throw "No args object defined for getChildren";
}
- if (options.node === undefined) {
- throw "No node defined on options object for getChildren";
+ if (!args.node) {
+ throw "No node defined on args object for getChildren";
}
- var section = options.section || 'content';
- var treeItem = options.node;
+ var section = args.section || 'content';
+ var treeItem = args.node;
//hack to have create as default content action
var action;
if(section === "content"){
action = "create";
}
-
- if (!options.node) {
- throw "No node defined";
- }
-
+
return treeResource.loadNodes({ section: section, node: treeItem })
.then(function(data) {
//now that we have the data, we need to add the level property to each item and the view
diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less
index f18e33637a..0375a918cf 100644
--- a/src/Umbraco.Web.UI.Client/src/less/tree.less
+++ b/src/Umbraco.Web.UI.Client/src/less/tree.less
@@ -40,6 +40,11 @@
width: 100%;
display: table
}
+
+.umb-tree ul.collapsed {
+ display:none;
+}
+
.umb-tree a {
vertical-align: middle;
display: inline-block;
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js
index 1ee3114a49..0b7ca0d4fd 100644
--- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js
@@ -15,11 +15,14 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio
contentResource.deleteById($scope.currentNode.id).then(function () {
$scope.currentNode.loading = false;
+
+ //get the root node before we remove it
+ var rootNode = treeService.getTreeRoot($scope.currentNode);
+
//TODO: Need to sync tree, etc...
treeService.removeNode($scope.currentNode);
- //ensure the recycle bin has child nodes now
- var rootNode = treeService.getTreeRoot($scope.currentNode);
+ //ensure the recycle bin has child nodes now
var recycleBin = treeService.getDescendantNode(rootNode, -20);
recycleBin.hasChildren = true;
diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html
index b82dcb7815..641fe465bf 100644
--- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html
+++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html
@@ -9,7 +9,7 @@
-
+ ng-click="executeMenuItem(currentNode,action,currentSection)">
diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs
index e090420662..b2fe353289 100644
--- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs
+++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs
@@ -113,19 +113,24 @@ namespace Umbraco.Web.Trees
throw new InvalidOperationException("Could not create root node for tree " + configTree.Alias);
}
- return new SectionRootNode(
+ var sectionRoot = new SectionRootNode(
rootId,
rootNode.Result.MenuUrl)
{
Title = rootNode.Result.Title,
Children = byControllerAttempt.Result
};
+ foreach (var d in rootNode.Result.AdditionalData)
+ {
+ sectionRoot.AdditionalData[d.Key] = d.Value;
+ }
+ return sectionRoot;
}
var legacyAttempt = configTree.TryLoadFromLegacyTree(id, queryStrings, Url, configTree.ApplicationAlias);
if (legacyAttempt.Success)
{
- return new SectionRootNode(
+ var sectionRoot = new SectionRootNode(
rootId,
Url.GetUmbracoApiService("GetMenu", rootId)
+ "&parentId=" + rootId
@@ -134,6 +139,8 @@ namespace Umbraco.Web.Trees
{
Children = legacyAttempt.Result
};
+ sectionRoot.AdditionalData.Add("treeType", Type.GetType(configTree.Type).FullName);
+ return sectionRoot;
}
throw new ApplicationException("Could not render a tree for type " + configTree.Alias);
diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs
index b8475ac059..ab060d8c08 100644
--- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs
+++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Mvc;
using Umbraco.Core;
+using Umbraco.Web.Trees.Menu;
using Umbraco.Web.WebApi;
using umbraco.BusinessLogic;
using umbraco.cms.presentation.Trees;
diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs
index cfdd80b6dd..8684ddebaf 100644
--- a/src/Umbraco.Web/Trees/ContentTreeController.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeController.cs
@@ -9,6 +9,7 @@ using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Services;
+using Umbraco.Web.Trees.Menu;
using umbraco;
using umbraco.BusinessLogic.Actions;
using umbraco.businesslogic;
@@ -134,7 +135,7 @@ namespace Umbraco.Web.Trees
// default actions for all users
allowedMenu.AddMenuItem();
- allowedMenu.AddMenuItem(true);
+ menu.AddMenuItem(true);
return allowedMenu;
}
@@ -172,7 +173,9 @@ namespace Umbraco.Web.Trees
menu.AddMenuItem(true);
menu.AddMenuItem(true);
menu.AddMenuItem();
- menu.AddMenuItem(true);
+
+ menu.AddMenuItem(true);
+
return menu;
}
diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
index 4968a8256f..355ac04741 100644
--- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
@@ -4,6 +4,7 @@ using System.Net.Http.Formatting;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
+using Umbraco.Web.Trees.Menu;
using umbraco;
using umbraco.BusinessLogic.Actions;
diff --git a/src/Umbraco.Web/Trees/LegacyTreeController.cs b/src/Umbraco.Web/Trees/LegacyTreeController.cs
index d89496c55f..442d751af4 100644
--- a/src/Umbraco.Web/Trees/LegacyTreeController.cs
+++ b/src/Umbraco.Web/Trees/LegacyTreeController.cs
@@ -6,6 +6,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Mvc;
+using Umbraco.Web.Trees.Menu;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs
index b2ad9b45e8..1b0db2e23c 100644
--- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs
+++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs
@@ -5,6 +5,7 @@ using System.Web;
using System.Web.Http.Routing;
using Umbraco.Core;
using Umbraco.Core.IO;
+using Umbraco.Web.Trees.Menu;
using umbraco;
using umbraco.BusinessLogic.Actions;
using umbraco.cms.helpers;
diff --git a/src/Umbraco.Web/Trees/Menu/ActionMenuItem.cs b/src/Umbraco.Web/Trees/Menu/ActionMenuItem.cs
new file mode 100644
index 0000000000..a0227c2f1f
--- /dev/null
+++ b/src/Umbraco.Web/Trees/Menu/ActionMenuItem.cs
@@ -0,0 +1,30 @@
+using System;
+using Umbraco.Core;
+
+namespace Umbraco.Web.Trees.Menu
+{
+ ///
+ /// A menu item that represents some JS that needs to execute when the menu item is clicked.
+ ///
+ ///
+ /// These types of menu items are rare but they do exist. Things like refresh node simply execute
+ /// JS and don't launch a dialog.
+ ///
+ /// Each action menu item describes what angular service that it's method exists in and what the method name is
+ ///
+ public abstract class ActionMenuItem : MenuItem
+ {
+ protected ActionMenuItem()
+ : base()
+ {
+ var attribute = GetType().GetCustomAttribute(false);
+ if (attribute == null)
+ {
+ throw new InvalidOperationException("All " + typeof (ActionMenuItem).FullName + " instances must be attributed with " + typeof (ActionMenuItemAttribute).FullName);
+ }
+
+ //add the current type to the metadata
+ AdditionalData.Add("jsAction", string.Format("{0}.{1}", attribute.ServiceName, attribute.MethodName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Trees/Menu/ActionMenuItemAttribute.cs b/src/Umbraco.Web/Trees/Menu/ActionMenuItemAttribute.cs
new file mode 100644
index 0000000000..c9efd67f68
--- /dev/null
+++ b/src/Umbraco.Web/Trees/Menu/ActionMenuItemAttribute.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Umbraco.Web.Trees.Menu
+{
+ ///
+ /// The attribute to assign to any ActionMenuItem objects.
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class ActionMenuItemAttribute : Attribute
+ {
+ public ActionMenuItemAttribute(string serviceName, string methodName)
+ {
+ MethodName = methodName;
+ ServiceName = serviceName;
+ }
+
+ public string MethodName { get; private set; }
+ public string ServiceName { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Trees/MenuItem.cs b/src/Umbraco.Web/Trees/Menu/MenuItem.cs
similarity index 92%
rename from src/Umbraco.Web/Trees/MenuItem.cs
rename to src/Umbraco.Web/Trees/Menu/MenuItem.cs
index 1e0ce12240..b8ed90926a 100644
--- a/src/Umbraco.Web/Trees/MenuItem.cs
+++ b/src/Umbraco.Web/Trees/Menu/MenuItem.cs
@@ -3,8 +3,11 @@ using System.Runtime.Serialization;
using umbraco.interfaces;
using System.Collections.Generic;
-namespace Umbraco.Web.Trees
+namespace Umbraco.Web.Trees.Menu
{
+ ///
+ /// A context menu item
+ ///
[DataContract(Name = "menuItem", Namespace = "")]
public class MenuItem
{
@@ -23,7 +26,7 @@ namespace Umbraco.Web.Trees
Action = legacyMenu;
}
- internal IAction Action { get; private set; }
+ internal IAction Action { get; set; }
///
/// A dictionary to support any additional meta data that should be rendered for the node which is
diff --git a/src/Umbraco.Web/Trees/MenuItemCollection.cs b/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs
similarity index 62%
rename from src/Umbraco.Web/Trees/MenuItemCollection.cs
rename to src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs
index c0e0e5429a..d21bc1e05f 100644
--- a/src/Umbraco.Web/Trees/MenuItemCollection.cs
+++ b/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs
@@ -4,8 +4,9 @@ using System.Runtime.Serialization;
using Umbraco.Core;
using umbraco.interfaces;
-namespace Umbraco.Web.Trees
+namespace Umbraco.Web.Trees.Menu
{
+
[CollectionDataContract(Name = "menuItems", Namespace = "")]
public class MenuItemCollection : IEnumerable