From b09a743beba2f5d7b5e6cb0db1239bfb30c2ab22 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 3 Oct 2013 11:25:26 +1000 Subject: [PATCH] Gets view paths to load in dynamically based on whether a core or plugin tree is rendering, this allows us to store view files based on conventions for packages. Completes: U4-2849 Ensure editor views, etc... can exist outside of the /umbraco folder for package devs --- .../common/mocks/umbraco.servervariables.js | 5 ++ .../src/common/services/navigation.service.js | 13 ++-- .../src/common/services/tree.service.js | 27 +++++++++ src/Umbraco.Web.UI.Client/src/routes.js | 31 +++++++--- .../unit/common/services/tree-service.spec.js | 18 +++++- .../MyPackage/Trees/LegacyTestTree.cs | 8 ++- .../Editors/BackOfficeController.cs | 34 +++++++++++ src/Umbraco.Web/PluginManagerExtensions.cs | 3 +- .../Trees/ApplicationTreeRegistrar.cs | 3 + .../Trees/ContentTreeController.cs | 1 + .../Trees/DataTypeTreeController.cs | 1 + src/Umbraco.Web/Trees/MediaTreeController.cs | 1 + src/Umbraco.Web/Trees/MemberTreeController.cs | 1 + .../Trees/Menu/MenuItemCollection.cs | 36 ++--------- .../Trees/Menu/MenuItemExtensions.cs | 15 ----- src/Umbraco.Web/Trees/TreeAttribute.cs | 12 ++++ src/Umbraco.Web/Trees/TreeController.cs | 59 +------------------ 17 files changed, 146 insertions(+), 122 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index 058a8366ae..d4fe75e80c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -23,5 +23,10 @@ Umbraco.Sys.ServerVariables = { "appPluginsPath" : "/App_Plugins", "imageFileTypes": "jpeg,jpg,gif,bmp,png,tiff,tif" }, + umbracoPlugins: { + trees: [ + { alias: "myTree", packageFolder: "MyPackage" } + ] + }, isDebuggingEnabled: true }; \ No newline at end of file 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 eca8f70576..70d7dc577c 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 @@ -364,16 +364,19 @@ angular.module('umbraco.services') //by convention we will look into the /views/{treetype}/{action}.html // for example: /views/content/create.html - //we will also check for a 'packageName' in metaData, if it exists, we'll look by convention in that folder + //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: // for example: /App_Plugins/{mypackage}/umbraco/{treetype}/create.html - if (args.action.metaData["packageName"]) { + var treeAlias = treeService.getTreeAlias(args.node); + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); - templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/umbraco/views/" + treeService.getTreeAlias(args.node) + "/" + args.action.alias + ".html"; + if (packageTreeFolder) { + templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/umbraco/" + treeAlias + "/" + args.action.alias + ".html"; } else { - templateUrl = "views/" + treeService.getTreeAlias(args.node) + "/" + args.action.alias + ".html"; + templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html"; } iframe = false; 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 f241ea6cab..b34cd72c79 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 @@ -58,6 +58,33 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc } }, + /** + * @ngdoc method + * @name umbraco.services.treeService#getTreePackageFolder + * @methodOf umbraco.services.treeService + * @function + * + * @description + * Determines if the current tree is a plugin tree and if so returns the package folder it has declared + * so we know where to find it's views, otherwise it will just return undefined. + * + * @param {String} treeAlias The tree alias to check + */ + getTreePackageFolder: function(treeAlias) { + //we determine this based on the server variables + if (Umbraco.Sys.ServerVariables.umbracoPlugins && + Umbraco.Sys.ServerVariables.umbracoPlugins.trees && + angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { + + var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) { + return item.alias === treeAlias; + }); + + return found ? found.packageFolder : undefined; + } + return undefined; + }, + /** clears the tree cache */ clearCache: function() { treeArray = []; diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 792fa23bd7..94ec046e81 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -84,21 +84,34 @@ app.config(function ($routeProvider) { resolve: checkAuth(true) }) .when('/:section/:tree/:method/:id', { - templateUrl: function (rp) { - if (!rp.tree || !rp.method) { - return "views/common/dashboard.html"; + //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. + template: "
", + //This controller will execute for this route, then we replace the template dynamnically based on the current tree. + controller: function ($scope, $route, $routeParams, treeService) { + + if (!$routeParams.tree || !$routeParams.method) { + $scope.templateUrl = "views/common/dashboard.html"; } - - //TODO: Here we need to figure out if this route is for a package and if so then we need + + // Here we need to figure out if this route is for a package tree and if so then we need // to change it's convention view path to: // /App_Plugins/{mypackage}/umbraco/{treetype}/{method}.html + // otherwise if it is a core tree we use the core paths: + // views/{treetype}/{method}.html - //we don't need to put views into section folders since theoretically trees - // could be moved among sections, we only need folders for specific trees. + var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); + + if (packageTreeFolder) { + $scope.templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/umbraco/" + $routeParams.tree + "/" + $routeParams.method + ".html"; + } + else { + $scope.templateUrl = 'views/' + $routeParams.tree + '/' + $routeParams.method + '.html'; + } - return 'views/' + rp.tree + '/' + rp.method + '.html'; - }, + }, resolve: checkAuth(true) }) .otherwise({ redirectTo: '/login' }); 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 f5ed2935a4..230a5ad015 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 @@ -40,7 +40,23 @@ describe('tree service tests', function () { beforeEach(inject(function ($injector) { treeService = $injector.get('treeService'); - })); + })); + + describe('lookup plugin based trees', function() { + + it('can find a plugin based tree', function () { + //we know this exists in the mock umbraco server vars + var found = treeService.getTreePackageFolder("myTree"); + expect(found).toBe("MyPackage"); + }); + + it('returns undefined for a not found tree', function () { + //we know this exists in the mock umbraco server vars + var found = treeService.getTreePackageFolder("asdfasdf"); + expect(found).not.toBeDefined(); + }); + + }); describe('query existing node structure of the tree', function () { diff --git a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Trees/LegacyTestTree.cs b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Trees/LegacyTestTree.cs index 347c55503a..4971384529 100644 --- a/src/Umbraco.Web.UI/App_Plugins/MyPackage/Trees/LegacyTestTree.cs +++ b/src/Umbraco.Web.UI/App_Plugins/MyPackage/Trees/LegacyTestTree.cs @@ -30,9 +30,11 @@ namespace Umbraco.Web.UI.App_Plugins.MyPackage.Trees } protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - MenuItems.AddMenuItem(new MenuItem("create", "Create")); - return MenuItems; + { + var menu = new MenuItemCollection(); + + menu.AddMenuItem(new MenuItem("create", "Create")); + return menu; } } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 641d809ca7..dabbd6b12c 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Web.Mvc; using Umbraco.Core.Configuration; @@ -85,12 +86,45 @@ namespace Umbraco.Web.Editors string.Join(",",UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes)}, } }, + { + "umbracoPlugins", new Dictionary + { + {"trees", GetTreePluginsMetaData()} + } + }, { "isDebuggingEnabled", HttpContext.IsDebuggingEnabled } }; return JavaScript(ServerVariablesParser.Parse(d)); } + private IEnumerable> GetTreePluginsMetaData() + { + var treeTypes = PluginManager.Current.ResolveAttributedTreeControllers(); + //get all plugin trees with their attributes + var treesWithAttributes = treeTypes.Select(x => new + { + tree = x, attributes = + x.GetCustomAttributes(false) + }).ToArray(); + + var pluginTreesWithAttributes = treesWithAttributes + //don't resolve any tree decorated with CoreTreeAttribute + .Where(x => x.attributes.All(a => (a is CoreTreeAttribute) == false)) + //we only care about trees with the PluginControllerAttribute + .Where(x => x.attributes.Any(a => a is PluginControllerAttribute)) + .ToArray(); + + return (from p in pluginTreesWithAttributes + let treeAttr = p.attributes.OfType().Single() + let pluginAttr = p.attributes.OfType().Single() + select new Dictionary + { + {"alias", treeAttr.Alias}, {"packageFolder", pluginAttr.AreaName} + }).ToArray(); + + } + /// /// Returns the JavaScript blocks for any legacy trees declared /// diff --git a/src/Umbraco.Web/PluginManagerExtensions.cs b/src/Umbraco.Web/PluginManagerExtensions.cs index ab0846f530..bda4b3a95f 100644 --- a/src/Umbraco.Web/PluginManagerExtensions.cs +++ b/src/Umbraco.Web/PluginManagerExtensions.cs @@ -25,8 +25,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(); } internal static IEnumerable ResolveSurfaceControllers(this PluginManager resolver) diff --git a/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs b/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs index 2cdd43be38..6666936c70 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeRegistrar.cs @@ -7,6 +7,9 @@ using umbraco.businesslogic; namespace Umbraco.Web.Trees { + //TODO: Is there any way to get this to execute lazily when needed? + // i.e. When the back office loads so that this doesn't execute on startup for a content request. + /// /// A startup handler for putting the tree config in the config file based on attributes found /// diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 080ca2a559..1d66121de2 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -22,6 +22,7 @@ namespace Umbraco.Web.Trees [LegacyBaseTree(typeof(loadContent))] [Tree(Constants.Applications.Content, Constants.Trees.Content, "Content")] [PluginController("UmbracoTrees")] + [CoreTree] public class ContentTreeController : ContentTreeControllerBase { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 348fa88628..a5203da563 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -15,6 +15,7 @@ namespace Umbraco.Web.Trees { [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, "Data Types")] [PluginController("UmbracoTrees")] + [CoreTree] public class DataTypeTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 8f806f980e..22fabe5da8 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -18,6 +18,7 @@ namespace Umbraco.Web.Trees [LegacyBaseTree(typeof(loadMedia))] [Tree(Constants.Applications.Media, Constants.Trees.Media, "Media")] [PluginController("UmbracoTrees")] + [CoreTree] public class MediaTreeController : ContentTreeControllerBase { protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index 50c22812eb..8350662265 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -17,6 +17,7 @@ namespace Umbraco.Web.Trees [LegacyBaseTree(typeof (loadMembers))] [Tree(Constants.Applications.Members, Constants.Trees.Members, "Members")] [PluginController("UmbracoTrees")] + [CoreTree] public class MemberTreeController : TreeController { protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs b/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs index 5e4f305848..bd71a8d595 100644 --- a/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs +++ b/src/Umbraco.Web/Trees/Menu/MenuItemCollection.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Trees.Menu [DataContract(Name = "menuItems", Namespace = "")] public class MenuItemCollection { - private readonly string _packageFolderName; + //private readonly string _packageFolderName; private readonly List _menuItems; public MenuItemCollection() @@ -24,19 +24,7 @@ namespace Umbraco.Web.Trees.Menu { _menuItems = new List(items); } - - public MenuItemCollection(string packageFolderName) - : this() - { - _packageFolderName = packageFolderName; - } - - public MenuItemCollection(string packageFolderName, IEnumerable items) - { - _packageFolderName = packageFolderName; - _menuItems = new List(items); - } - + /// /// 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. /// @@ -63,7 +51,7 @@ namespace Umbraco.Web.Trees.Menu DetectLegacyActionMenu(action.GetType(), item); - AddItemToCollection(item); + _menuItems.Add(item); return item; } @@ -81,7 +69,7 @@ namespace Umbraco.Web.Trees.Menu /// public void AddMenuItem(MenuItem item) { - AddItemToCollection(item); + _menuItems.Add(item); } /// @@ -109,7 +97,7 @@ namespace Umbraco.Web.Trees.Menu Action = item.Action }; - AddItemToCollection(customMenuItem); + _menuItems.Add(customMenuItem); return customMenuItem; } @@ -152,7 +140,7 @@ namespace Umbraco.Web.Trees.Menu var item = CreateMenuItem(name, hasSeparator, additionalData); if (item != null) { - AddItemToCollection(item); + _menuItems.Add(item); return item; } return null; @@ -224,18 +212,6 @@ namespace Umbraco.Web.Trees.Menu } } - /// - /// This handles adding a menu item to the internal collection and will configure it accordingly - /// - /// - private void AddItemToCollection(MenuItem menuItem) - { - if (_packageFolderName.IsNullOrWhiteSpace() == false) - { - menuItem.SetPackageFolder(_packageFolderName); - } - _menuItems.Add(menuItem); - } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs b/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs index 019b42ab19..32dfecc13a 100644 --- a/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs +++ b/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs @@ -6,11 +6,6 @@ namespace Umbraco.Web.Trees.Menu { public static class MenuItemExtensions { - /// - /// Used as a key for the AdditionalData to specify which package folder the menu launcher should look in for views - /// - internal const string PackageName = "packageName"; - /// /// Used as a key for the AdditionalData to specify a specific dialog title instead of the menu title /// @@ -32,16 +27,6 @@ namespace Umbraco.Web.Trees.Menu /// internal const string ActionViewKey = "actionView"; - /// - /// Sets the package folder to look for views in - /// - /// - /// - internal static void SetPackageFolder(this MenuItem menuItem, string packageFolder) - { - menuItem.AdditionalData[PackageName] = packageFolder; - } - /// /// Sets the menu item to display a dialog based on an angular view path /// diff --git a/src/Umbraco.Web/Trees/TreeAttribute.cs b/src/Umbraco.Web/Trees/TreeAttribute.cs index 72ae4044e9..5acf367738 100644 --- a/src/Umbraco.Web/Trees/TreeAttribute.cs +++ b/src/Umbraco.Web/Trees/TreeAttribute.cs @@ -2,6 +2,18 @@ namespace Umbraco.Web.Trees { + /// + /// Indicates that a tree is a core tree and shouldn't be treated as a plugin tree + /// + /// + /// This ensures that umbraco will look in the umbraco folders for views for this tree + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + internal class CoreTreeAttribute : Attribute + { + + } + /// /// Identifies an application tree /// diff --git a/src/Umbraco.Web/Trees/TreeController.cs b/src/Umbraco.Web/Trees/TreeController.cs index e7df099c53..c624d75656 100644 --- a/src/Umbraco.Web/Trees/TreeController.cs +++ b/src/Umbraco.Web/Trees/TreeController.cs @@ -18,7 +18,6 @@ namespace Umbraco.Web.Trees public abstract class TreeController : UmbracoAuthorizedApiController { private readonly TreeAttribute _attribute; - private readonly MenuItemCollection _menuItemCollection; /// /// Remove the xml formatter... only support JSON! @@ -45,21 +44,8 @@ namespace Umbraco.Web.Trees //assign the properties of this object to those of the metadata attribute _attribute = treeAttributes.First(); - - //Create the menu item collection with the area nem already specified - _menuItemCollection = Metadata.AreaName.IsNullOrWhiteSpace() - ? new MenuItemCollection() - : new MenuItemCollection(Metadata.AreaName); } - - /// - /// Returns a menu item collection to be used to return the menu items from GetMenuForNode - /// - public MenuItemCollection MenuItems - { - get { return _menuItemCollection; } - } - + /// /// The method called to render the contents of the tree structure /// @@ -334,47 +320,6 @@ namespace Umbraco.Web.Trees if (handler != null) handler(instance, e); } #endregion - - #region Metadata - /// - /// stores the metadata about plugin controllers - /// - private static readonly ConcurrentDictionary MetadataStorage = new ConcurrentDictionary(); - - /// - /// Returns the metadata for this instance - /// - internal PluginControllerMetadata Metadata - { - get { return GetMetadata(this.GetType()); } - } - - /// - /// Returns the metadata for a PluginController - /// - /// - /// - internal static PluginControllerMetadata GetMetadata(Type type) - { - - return MetadataStorage.GetOrAdd(type, type1 => - { - var attribute = type.GetCustomAttribute(false); - - var meta = new PluginControllerMetadata() - { - AreaName = attribute == null ? null : attribute.AreaName, - ControllerName = ControllerExtensions.GetControllerName(type), - ControllerNamespace = type.Namespace, - ControllerType = type - }; - - MetadataStorage.TryAdd(type, meta); - - return meta; - }); - - } - #endregion + } }