From e1cf3bc597e583efbab8c5bf15231cb65fd9a1e6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 26 Sep 2013 15:55:38 +1000 Subject: [PATCH] Implements: U4-2937 A tree item should be able to have a default action --- .../src/common/mocks/resources/tree.mocks.js | 47 +- .../src/common/services/navigation.service.js | 74 +- .../src/common/services/tree.service.js | 41 +- .../unit/common/services/tree-service.spec.js | 16 +- src/Umbraco.Web/PluginManagerExtensions.cs | 2 +- .../Trees/ApplicationTreeExtensions.cs | 6 +- .../Trees/ContentTreeController.cs | 64 +- .../Trees/ContentTreeControllerBase.cs | 26 +- .../Trees/DataTypeTreeController.cs | 2 +- .../Trees/LegacyTreeDataConverter.cs | 8 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 3 + .../Trees/Menu/MenuItemCollection.cs | 41 +- ...TreeApiController.cs => TreeController.cs} | 638 +++++++++--------- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 14 files changed, 477 insertions(+), 493 deletions(-) rename src/Umbraco.Web/Trees/{TreeApiController.cs => TreeController.cs} (94%) diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js index 603f7acf8e..881c0f8b67 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/tree.mocks.js @@ -31,7 +31,12 @@ angular.module('umbraco.mocks'). { seperator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} } ]; - return [200, menu, null]; + var result = { + menuItems: menu, + defaultAlias: "create" + }; + + return [200, result, null]; } function returnChildren(status, data, headers) { @@ -54,10 +59,10 @@ angular.module('umbraco.mocks'). } var children = [ - { name: "child-of-" + section, childNodesUrl: url, id: level + "" + 1234, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1235, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1236, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1237, icon: "icon-document", routePath: "common/legacy/1237?p=" + encodeURI("developer/contentType.aspx?idequal1234"), children: [], expanded: false, hasChildren: true, level: level, defaultAction: action, menuUrl: menuUrl } + { name: "child-of-" + section, childNodesUrl: url, id: level + "" + 1234, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1235, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1236, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: level + "" + 1237, icon: "icon-document", routePath: "common/legacy/1237?p=" + encodeURI("developer/contentType.aspx?idequal1234"), children: [], expanded: false, hasChildren: true, level: level, menuUrl: menuUrl } ]; return [200, children, null]; @@ -69,10 +74,10 @@ angular.module('umbraco.mocks'). } var children = [ - { name: "Textstring", childNodesUrl: null, id: 10, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null }, - { name: "Multiple textstring", childNodesUrl: null, id: 11, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null }, - { name: "Yes/No", childNodesUrl: null, id: 12, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null }, - { name: "Rich Text Editor", childNodesUrl: null, id: 13, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, defaultAction: null, menuUrl: null } + { name: "Textstring", childNodesUrl: null, id: 10, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, + { name: "Multiple textstring", childNodesUrl: null, id: 11, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, + { name: "Yes/No", childNodesUrl: null, id: 12, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null }, + { name: "Rich Text Editor", childNodesUrl: null, id: 13, icon: "icon-document", children: [], expanded: false, hasChildren: false, level: 1, menuUrl: null } ]; return [200, children, null]; @@ -112,10 +117,10 @@ angular.module('umbraco.mocks'). name: "content", id: -1, children: [ - { name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: section + "/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl } + { name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-document", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: section + "/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } ], expanded: true, hasChildren: true, @@ -130,10 +135,10 @@ angular.module('umbraco.mocks'). name: "media", id: -1, children: [ - { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } ], expanded: true, hasChildren: true, @@ -189,10 +194,10 @@ angular.module('umbraco.mocks'). name: "randomTree", id: -1, children: [ - { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } ], expanded: true, hasChildren: true, 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 0918a1b71c..aee457fe3f 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 @@ -191,48 +191,48 @@ angular.module('umbraco.services') var deferred = $q.defer(); var self = this; - if (args.event !== undefined && args.node.defaultAction && !args.event.altKey) { - - treeService.getMenuItemByAlias({ treeNode: args.node, menuItemAlias: args.node.defaultAction }) - .then(function(result) { - - if (!result) { - throw "No menu item found with alias " + args.node.defaultAction; - } - - self.ui.currentNode = args.node; - - //ensure the current dialog is cleared before creating another! - if (self.ui.currentDialog) { - dialogService.close(self.ui.currentDialog); - } - - var dialog = self.showDialog({ - scope: args.scope, - node: args.node, - action: result, - section: self.ui.currentSection + treeService.getMenu({ treeNode: args.node }) + .then(function(data) { + + //check for a default + if (data.defaultAlias) { + var found = _.find(data.menuItems, function(item) { + return item.alias = data.defaultAlias; }); + if (found) { + + self.ui.currentNode = args.node; + //ensure the current dialog is cleared before creating another! + if (self.ui.currentDialog) { + dialogService.close(self.ui.currentDialog); + } - //return the dialog this is opening. - deferred.resolve(dialog); - }); - } - else { - setMode("menu"); + var dialog = self.showDialog({ + scope: args.scope, + node: args.node, + action: found, + section: self.ui.currentSection + }); - treeService.getMenu({ treeNode: args.node }) - .then(function(data) { - ui.actions = data; - }); + //return the dialog this is opening. + deferred.resolve(dialog); + return; + } + } - this.ui.currentNode = args.node; - this.ui.dialogTitle = args.node.name; - - //we're not opening a dialog, return null. - deferred.resolve(null); - } + //there is no default or we couldn't find one so just continue showing the menu + + setMode("menu"); + + ui.actions = data.menuItems; + + ui.currentNode = args.node; + ui.dialogTitle = args.node.name; + //we're not opening a dialog, return null. + deferred.resolve(null); + }); + return deferred.promise; }, 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 bc65bb94af..db8a892618 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 @@ -60,7 +60,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc /** * @ngdoc method - * @name umbraco.services.treeService#getMenuItemByAlias + * @name umbraco.services.treeService#loadNodeChildren * @methodOf umbraco.services.treeService * @function * @@ -237,39 +237,6 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc }); }, - /** - * @ngdoc method - * @name umbraco.services.treeService#getMenuItemByAlias - * @methodOf umbraco.services.treeService - * @function - * - * @description - * Attempts to return a tree node's menu item based on the alias supplied, otherwise returns null. - * @param {object} args An arguments object - * @param {object} args.treeNode The tree node to get the menu item for - * @param {object} args.menuItemAlias The menu item alias to attempt to find - */ - getMenuItemByAlias: function (args) { - - if (!args) { - throw "args cannot be null"; - } - if (!args.treeNode) { - throw "args.treeNode cannot be null"; - } - if (!args.menuItemAlias) { - throw "args.menuItemAlias cannot be null"; - } - - return this.getMenu(args) - .then(function (menuItems) { - //try to find the node with the alias - return _.find(menuItems, function(item) { - return item.alias === args.menuItemAlias; - }); - }); - }, - /** Gets the children from the server for a given node */ getChildren: function (args) { @@ -283,12 +250,6 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc var section = args.section || 'content'; var treeItem = args.node; - //hack to have create as default content action - var action; - if(section === "content"){ - action = "create"; - } - var self = this; return treeResource.loadNodes({ section: section, node: treeItem }) diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js index 4d87a24f56..f5ed2935a4 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js @@ -11,17 +11,17 @@ describe('tree service tests', function () { id: -1, children: [ { - name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl, + name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl, children: [ - { name: "random-name-1", childNodesUrl: url, id: 11, icon: "icon-home", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-2", childNodesUrl: url, id: 12, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-3", childNodesUrl: url, id: 13, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-4", childNodesUrl: url, id: 14, icon: "icon-folder-close", defaultAction: "create", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + { name: "random-name-1", childNodesUrl: url, id: 11, icon: "icon-home", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-2", childNodesUrl: url, id: 12, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-3", childNodesUrl: url, id: 13, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-4", childNodesUrl: url, id: 14, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } ] }, - { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-cogs", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, - { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: "content/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl } + { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-cogs", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-folder-close", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Recycle Bin", id: -20, childNodesUrl: url, icon: "icon-trash", routePath: "content/recyclebin", children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } ], expanded: true, hasChildren: true, diff --git a/src/Umbraco.Web/PluginManagerExtensions.cs b/src/Umbraco.Web/PluginManagerExtensions.cs index 7581971285..ab0846f530 100644 --- a/src/Umbraco.Web/PluginManagerExtensions.cs +++ b/src/Umbraco.Web/PluginManagerExtensions.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web internal static IEnumerable ResolveAttributedTreeControllers(this PluginManager resolver) { //don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason - return resolver.ResolveTypesWithAttribute(cacheResult: false); + return resolver.ResolveTypesWithAttribute(cacheResult: false); } internal static IEnumerable ResolveSurfaceControllers(this PluginManager resolver) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs index 105217b96f..e074496c15 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeExtensions.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Trees { //get reference to all TreeApiControllers var controllerTrees = UmbracoApiControllerResolver.Current.RegisteredUmbracoApiControllers - .Where(TypeHelper.IsTypeAssignableFrom) + .Where(TypeHelper.IsTypeAssignableFrom) .ToArray(); //find the one we're looking for @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees } var foundControllerTree = foundControllerTreeAttempt.Result; //instantiate it, since we are proxying, we need to setup the instance with our current context - var instance = (TreeApiController)DependencyResolver.Current.GetService(foundControllerTree); + var instance = (TreeController)DependencyResolver.Current.GetService(foundControllerTree); instance.ControllerContext = controllerContext; instance.Request = controllerContext.Request; //return the root @@ -67,7 +67,7 @@ namespace Umbraco.Web.Trees var foundControllerTree = foundControllerTreeAttempt.Result; //instantiate it, since we are proxying, we need to setup the instance with our current context - var instance = (TreeApiController)DependencyResolver.Current.GetService(foundControllerTree); + var instance = (TreeController)DependencyResolver.Current.GetService(foundControllerTree); instance.ControllerContext = controllerContext; instance.Request = controllerContext.Request; //return it's data diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index b330d41500..039c781163 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -54,9 +54,6 @@ namespace Umbraco.Web.Trees { node = base.CreateRootNode(queryStrings); } - - //InjectLegacyTreeCompatibility(node, queryStrings); - return node; } @@ -81,7 +78,7 @@ namespace Umbraco.Web.Trees { var e = (UmbracoEntity)entity; - var allowedUserOptions = GetUserMenuItemsForNode(e); + var allowedUserOptions = GetAllowedUserMenuItemsForNode(e); if (CanUserAccessNode(e, allowedUserOptions)) { //TODO: if the node is of a specific type, don't list its children @@ -99,8 +96,6 @@ namespace Umbraco.Web.Trees e.ContentTypeIcon, hasChildren); - //InjectLegacyTreeCompatibility(node, queryStrings); - nodes.Add(node); } } @@ -109,10 +104,13 @@ namespace Umbraco.Web.Trees protected override MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings) { - var menu = new MenuItemCollection(); - if (id == Constants.System.Root.ToInvariantString()) { + var menu = new MenuItemCollection(); + + //set the default to create + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + // we need to get the default permissions as you can't set permissions on the very root node var nodeActions = global::umbraco.BusinessLogic.Actions.Action.FromString( UmbracoUser.GetPermissions(Constants.System.Root.ToInvariantString())) @@ -123,19 +121,20 @@ namespace Umbraco.Web.Trees menu.AddMenuItem(); //filter the standard items - var allowedMenu = GetUserAllowedMenuItems(menu, nodeActions); + FilterUserAllowedMenuItems(menu, nodeActions); - if (allowedMenu.Any()) + if (menu.MenuItems.Any()) { - allowedMenu.Last().SeperatorBefore = true; + menu.MenuItems.Last().SeperatorBefore = true; } // add default actions for *all* users - allowedMenu.AddMenuItem().ConvertLegacyMenuItem(null, "content", "content"); - allowedMenu.AddMenuItem(true); - return allowedMenu; + menu.AddMenuItem().ConvertLegacyMenuItem(null, "content", "content"); + menu.AddMenuItem(true); + return menu; } + //return a normal node menu: int iid; if (int.TryParse(id, out iid) == false) @@ -148,9 +147,15 @@ namespace Umbraco.Web.Trees throw new HttpResponseException(HttpStatusCode.NotFound); } - return GetUserAllowedMenuItems( - CreateAllowedActions(item), - GetUserMenuItemsForNode(item)); + var nodeMenu = GetAllNodeMenuItems(item); + var allowedMenuItems = GetAllowedUserMenuItemsForNode(item); + + FilterUserAllowedMenuItems(nodeMenu, allowedMenuItems); + + //set the default to create + nodeMenu.DefaultMenuAlias = ActionNew.Instance.Alias; + + return nodeMenu; } protected override UmbracoObjectTypes UmbracoObjectType @@ -158,7 +163,12 @@ namespace Umbraco.Web.Trees get { return UmbracoObjectTypes.Document; } } - protected IEnumerable CreateAllowedActions(IUmbracoEntity item) + /// + /// Returns a collection of all menu items that can be on a content node + /// + /// + /// + protected MenuItemCollection GetAllNodeMenuItems(IUmbracoEntity item) { var menu = new MenuItemCollection(); menu.AddMenuItem(); @@ -185,23 +195,5 @@ namespace Umbraco.Web.Trees return menu; } - ///// - ///// This is required so that the legacy tree dialog pickers and the legacy TreeControl.ascx stuff works with these new trees. - ///// - ///// - ///// - ///// - ///// NOTE: That if developers create brand new trees using webapi controllers then they'll need to manually make it - ///// compatible with the legacy tree pickers, 99.9% of the time devs won't be doing that and once we make the new tree - ///// pickers and devs update their apps to be angularized it won't matter - ///// - //private void InjectLegacyTreeCompatibility(TreeNode node, FormDataCollection queryStrings) - //{ - // if (IsDialog(queryStrings)) - // { - // node.AdditionalData["legacyDialogAction"] = "javascript:openContent(" + node.NodeId + ");"; - // } - //} - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index fe8901db35..b3ebabb0f9 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -11,7 +11,7 @@ using umbraco.BusinessLogic.Actions; namespace Umbraco.Web.Trees { - public abstract class ContentTreeControllerBase : TreeApiController + public abstract class ContentTreeControllerBase : TreeController { /// /// Returns the @@ -99,19 +99,27 @@ namespace Umbraco.Web.Trees /// /// Based on the allowed actions, this will filter the ones that the current user is allowed /// - /// + /// /// /// - protected MenuItemCollection GetUserAllowedMenuItems(IEnumerable allMenuItems, IEnumerable userAllowedMenuItems) + protected void FilterUserAllowedMenuItems(MenuItemCollection menuWithAllItems, IEnumerable userAllowedMenuItems) { var userAllowedActions = userAllowedMenuItems.Where(x => x.Action != null).Select(x => x.Action).ToArray(); - return new MenuItemCollection(allMenuItems.Where( - a => (a.Action == null - || a.Action.CanBePermissionAssigned == false - || (a.Action.CanBePermissionAssigned && userAllowedActions.Contains(a.Action))))); + + var notAllowed = menuWithAllItems.MenuItems.Where( + a => (a.Action != null + && a.Action.CanBePermissionAssigned + && (a.Action.CanBePermissionAssigned == false || userAllowedActions.Contains(a.Action) == false))) + .ToArray(); + + //remove the ones that aren't allowed. + foreach (var m in notAllowed) + { + menuWithAllItems.RemoveMenuItem(m); + } } - internal MenuItemCollection GetUserMenuItemsForNode(IUmbracoEntity dd) + internal IEnumerable GetAllowedUserMenuItemsForNode(IUmbracoEntity dd) { var actions = global::umbraco.BusinessLogic.Actions.Action.FromString(UmbracoUser.GetPermissions(dd.Path)); @@ -119,7 +127,7 @@ namespace Umbraco.Web.Trees if (dd.CreatorId == UmbracoUser.Id && actions.Contains(ActionDelete.Instance) == false) actions.Add(ActionDelete.Instance); - return new MenuItemCollection(actions.Select(x => new MenuItem(x))); + return actions.Select(x => new MenuItem(x)); } /// diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index e287888def..278b4a188b 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Trees { [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, "Data Types")] [PluginController("UmbracoTrees")] - public class DataTypeTreeController : TreeApiController + public class DataTypeTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 787386179e..d7fb72d053 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.Trees var legacyAtt = controllerAttempt.Result.GetCustomAttribute(false); if (legacyAtt == null) { - LogHelper.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeApiController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute)); + LogHelper.Warn("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute)); return null; } @@ -143,7 +143,7 @@ namespace Umbraco.Web.Trees if (t is ContextMenuSeperator && numAdded > 0) { //store the index for which the seperator should be placed - seperators.Add(collection.Count()); + seperators.Add(collection.MenuItems.Count()); } else { @@ -164,12 +164,12 @@ namespace Umbraco.Web.Trees numAdded++; } } - var length = collection.Count(); + var length = collection.MenuItems.Count(); foreach (var s in seperators) { if (length >= s) { - collection.ElementAt(s).SeperatorBefore = true; + collection.MenuItems.ElementAt(s).SeperatorBefore = true; } } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 65a15dc2e9..4c2a3dd3c0 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -47,6 +47,9 @@ namespace Umbraco.Web.Trees { var menu = new MenuItemCollection(); + //set the default + menu.DefaultMenuAlias = ActionNew.Instance.Alias; + if (id == Constants.System.Root.ToInvariantString()) { // root actions diff --git a/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs b/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs index 1d680434e4..6f88ddfdb6 100644 --- a/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs +++ b/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs @@ -9,12 +9,14 @@ using umbraco.interfaces; namespace Umbraco.Web.Trees.Menu { - [CollectionDataContract(Name = "menuItems", Namespace = "")] - public class MenuItemCollection : IEnumerable + [DataContract(Name = "menuItems", Namespace = "")] + public class MenuItemCollection { + private readonly List _menuItems; + public MenuItemCollection() { - + _menuItems = new List(); } public MenuItemCollection(IEnumerable items) @@ -22,7 +24,20 @@ namespace Umbraco.Web.Trees.Menu _menuItems = new List(items); } - private readonly List _menuItems = new List(); + /// + /// Sets the default menu item alias to be shown when the menu is launched - this is optional and if not set then the menu will just be shown normally. + /// + [DataMember(Name = "defaultAlias")] + public string DefaultMenuAlias { get; set; } + + /// + /// The list of menu items + /// + [DataMember(Name = "menuItems")] + public IEnumerable MenuItems + { + get { return _menuItems; } + } /// /// Adds a menu item @@ -37,6 +52,15 @@ namespace Umbraco.Web.Trees.Menu return item; } + /// + /// Removes a menu item + /// + /// + public void RemoveMenuItem(MenuItem item) + { + _menuItems.Remove(item); + } + /// /// Adds a menu item /// @@ -167,14 +191,5 @@ namespace Umbraco.Web.Trees.Menu } } - public IEnumerator GetEnumerator() - { - return _menuItems.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/TreeApiController.cs b/src/Umbraco.Web/Trees/TreeController.cs similarity index 94% rename from src/Umbraco.Web/Trees/TreeApiController.cs rename to src/Umbraco.Web/Trees/TreeController.cs index bd88348b4d..e6ea6abe75 100644 --- a/src/Umbraco.Web/Trees/TreeApiController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -1,319 +1,319 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using Umbraco.Core; -using Umbraco.Web.Trees.Menu; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; - -namespace Umbraco.Web.Trees -{ - /// - /// The base controller for all tree requests - /// - public abstract class TreeApiController : UmbracoAuthorizedApiController - { - private readonly TreeAttribute _attribute; - - /// - /// Remove the xml formatter... only support JSON! - /// - /// - protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext) - { - base.Initialize(controllerContext); - controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter); - } - - protected TreeApiController() - { - //Locate the tree attribute - var treeAttributes = GetType() - .GetCustomAttributes(typeof(TreeAttribute), false) - .OfType() - .ToArray(); - - if (treeAttributes.Any() == false) - { - throw new InvalidOperationException("The Tree controller is missing the " + typeof(TreeAttribute).FullName + " attribute"); - } - - //assign the properties of this object to those of the metadata attribute - _attribute = treeAttributes.First(); - } - - /// - /// The method called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - protected abstract TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings); - - /// - /// Returns the menu structure for the node - /// - /// - /// - /// - protected abstract MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings); - - /// - /// The name to display on the root node - /// - public virtual string RootNodeDisplayName - { - get { return _attribute.Title; } - } - - /// - /// Gets the current tree alias from the attribute assigned to it. - /// - public string TreeAlias - { - get { return _attribute.Alias; } - } - - /// - /// Returns the root node for the tree - /// - /// - /// - [HttpQueryStringFilter("queryStrings")] - public TreeNode GetRootNode(FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var node = CreateRootNode(queryStrings); - - //add the tree alias to the root - node.AdditionalData.Add("treeAlias", TreeAlias); - - AddQueryStringsToAdditionalData(node, queryStrings); - - //check if the tree is searchable and add that to the meta data as well - if (this is ISearchableTree) - { - node.AdditionalData.Add("searchable", "true"); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog(queryStrings)) - { - node.RoutePath = "#"; - } - - OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); - - return node; - } - - /// - /// The action called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// JSON markup for jsTree - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - [HttpQueryStringFilter("queryStrings")] - public TreeNodeCollection GetNodes(string id, FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var nodes = GetTreeNodes(id, queryStrings); - - foreach (var node in nodes) - { - AddQueryStringsToAdditionalData(node, queryStrings); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog((queryStrings))) - { - foreach (var node in nodes) - { - node.RoutePath = "#"; - } - } - - //raise the event - OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); - - return nodes; - } - - /// - /// The action called to render the menu for a tree node - /// - /// - /// - /// - [HttpQueryStringFilter("queryStrings")] - public MenuItemCollection GetMenu(string id, FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - return GetMenuForNode(id, queryStrings); - } - - /// - /// Helper method to create a root model for a tree - /// - /// - protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); - - var node = new TreeNode( - rootNodeAsString, - Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), - Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) - { - HasChildren = true, - RoutePath = currApp, - Title = RootNodeDisplayName - }; - - return node; - } - - /// - /// The AdditionalData of a node is always populated with the query string data, this method performs this - /// operation and ensures that special values are not inserted or that duplicate keys are not added. - /// - /// - /// - protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) - { - foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) - { - node.AdditionalData.Add(q.Key, q.Value); - } - } - - #region Create TreeNode methods - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title, Icon = icon }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, string routePath) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title, RoutePath = routePath, Icon = icon }; - return node; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, bool hasChildren) - { - var treeNode = CreateTreeNode(id, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) - { - var treeNode = CreateTreeNode(id, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - treeNode.RoutePath = routePath; - return treeNode; - } - - #endregion - - #region Query String parameter helpers - - /// - /// If the request is for a dialog mode tree - /// - /// - /// - protected bool IsDialog(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.DialogMode); - } - - #endregion - - public static event EventHandler TreeNodesRendering; - - private static void OnTreeNodesRendering(TreeApiController instance, TreeNodesRenderingEventArgs e) - { - var handler = TreeNodesRendering; - if (handler != null) handler(instance, e); - } - - public static event EventHandler RootNodeRendering; - - private static void OnRootNodeRendering(TreeApiController instance, TreeNodeRenderingEventArgs e) - { - var handler = RootNodeRendering; - if (handler != null) handler(instance, e); - } - } -} +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Web.Trees.Menu; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; + +namespace Umbraco.Web.Trees +{ + /// + /// The base controller for all tree requests + /// + public abstract class TreeController : UmbracoAuthorizedApiController + { + private readonly TreeAttribute _attribute; + + /// + /// Remove the xml formatter... only support JSON! + /// + /// + protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext) + { + base.Initialize(controllerContext); + controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter); + } + + protected TreeController() + { + //Locate the tree attribute + var treeAttributes = GetType() + .GetCustomAttributes(typeof(TreeAttribute), false) + .OfType() + .ToArray(); + + if (treeAttributes.Any() == false) + { + throw new InvalidOperationException("The Tree controller is missing the " + typeof(TreeAttribute).FullName + " attribute"); + } + + //assign the properties of this object to those of the metadata attribute + _attribute = treeAttributes.First(); + } + + /// + /// The method called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + protected abstract TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings); + + /// + /// Returns the menu structure for the node + /// + /// + /// + /// + protected abstract MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings); + + /// + /// The name to display on the root node + /// + public virtual string RootNodeDisplayName + { + get { return _attribute.Title; } + } + + /// + /// Gets the current tree alias from the attribute assigned to it. + /// + public string TreeAlias + { + get { return _attribute.Alias; } + } + + /// + /// Returns the root node for the tree + /// + /// + /// + [HttpQueryStringFilter("queryStrings")] + public TreeNode GetRootNode(FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var node = CreateRootNode(queryStrings); + + //add the tree alias to the root + node.AdditionalData.Add("treeAlias", TreeAlias); + + AddQueryStringsToAdditionalData(node, queryStrings); + + //check if the tree is searchable and add that to the meta data as well + if (this is ISearchableTree) + { + node.AdditionalData.Add("searchable", "true"); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog(queryStrings)) + { + node.RoutePath = "#"; + } + + OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); + + return node; + } + + /// + /// The action called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// JSON markup for jsTree + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + [HttpQueryStringFilter("queryStrings")] + public TreeNodeCollection GetNodes(string id, FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var nodes = GetTreeNodes(id, queryStrings); + + foreach (var node in nodes) + { + AddQueryStringsToAdditionalData(node, queryStrings); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog((queryStrings))) + { + foreach (var node in nodes) + { + node.RoutePath = "#"; + } + } + + //raise the event + OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); + + return nodes; + } + + /// + /// The action called to render the menu for a tree node + /// + /// + /// + /// + [HttpQueryStringFilter("queryStrings")] + public MenuItemCollection GetMenu(string id, FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + return GetMenuForNode(id, queryStrings); + } + + /// + /// Helper method to create a root model for a tree + /// + /// + protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); + var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); + + var node = new TreeNode( + rootNodeAsString, + Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), + Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) + { + HasChildren = true, + RoutePath = currApp, + Title = RootNodeDisplayName + }; + + return node; + } + + /// + /// The AdditionalData of a node is always populated with the query string data, this method performs this + /// operation and ensures that special values are not inserted or that duplicate keys are not added. + /// + /// + /// + protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) + { + foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) + { + node.AdditionalData.Add(q.Key, q.Value); + } + } + + #region Create TreeNode methods + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title, Icon = icon }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, string routePath) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, jsonUrl, menuUrl) { Title = title, RoutePath = routePath, Icon = icon }; + return node; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(id, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) + { + var treeNode = CreateTreeNode(id, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + return treeNode; + } + + #endregion + + #region Query String parameter helpers + + /// + /// If the request is for a dialog mode tree + /// + /// + /// + protected bool IsDialog(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.DialogMode); + } + + #endregion + + public static event EventHandler TreeNodesRendering; + + private static void OnTreeNodesRendering(TreeController instance, TreeNodesRenderingEventArgs e) + { + var handler = TreeNodesRendering; + if (handler != null) handler(instance, e); + } + + public static event EventHandler RootNodeRendering; + + private static void OnRootNodeRendering(TreeController instance, TreeNodeRenderingEventArgs e) + { + var handler = RootNodeRendering; + if (handler != null) handler(instance, e); + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 931ea94d4c..696a2d185b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -428,7 +428,7 @@ - +