diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js index 8c163918d7..fc629f974a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtree.directive.js @@ -24,18 +24,17 @@ angular.module("umbraco.directives") var hideheader = (attrs.showheader === 'false') ? true : false; var hideoptions = (attrs.showoptions === 'false') ? "hide-options" : ""; - - var template = ''; @@ -45,6 +44,22 @@ angular.module("umbraco.directives") return function (scope, element, attrs, controller) { + /** Helper function to emit tree events */ + function emitEvent(eventName, args) { + if (scope.callback) { + $(scope.callback).trigger(eventName, args); + } + } + + /** + 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 (e, n, ev) { + emitEvent("treeOptionsClick", { element: e, node: n, event: ev }); + }; + function loadTree() { if (scope.section) { 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 a4dc66fe8a..e77306a04f 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 @@ -74,39 +74,76 @@ angular.module('umbraco.mocks'). switch (section) { case "content": - t = [ + t = { + name: "content", + id: -1, + children: [ { name: "My website", id: 1234, childNodesUrl: url, icon: "icon-home", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, { name: "Components", id: 1235, childNodesUrl: url, icon: "icon-cogs", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, { name: "Archieve", id: 1236, childNodesUrl: url, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl }, { name: "Recycle Bin", id: 1237, childNodesUrl: url, icon: "icon-trash", view: section + "/trash/view/", children: [], expanded: false, hasChildren: true, level: 1, defaultAction: "create", menuUrl: menuUrl } - ]; + ], + expanded: true, + hasChildren: true, + level: 0, + menuUrl: menuUrl + }; + break; case "developer": - t = [ - { name: "Data types", childNodesUrl: url, id: 1234, icon: "icon-folder-close", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Macros", childNodesUrl: url, id: 1235, icon: "icon-folder-close", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Pacakges", childNodesUrl: url, id: 1236, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "XSLT Files", childNodesUrl: url, id: 1237, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Razor Files", childNodesUrl: url, id: 1237, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } - ]; + t = { + name: "developer", + id: -1, + children: [ + { name: "Data types", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Macros", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Pacakges", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "XSLT Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Razor Files", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + ], + expanded: true, + hasChildren: true, + level: 0, + isContainer: true + }; + break; case "settings": - t = [ - { name: "Stylesheets", childNodesUrl: url, id: 1234, icon: "icon-folder-close", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Templates", childNodesUrl: url, id: 1235, icon: "icon-folder-close", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Dictionary", childNodesUrl: url, id: 1236, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Media types", childNodesUrl: url, id: 1237, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "Document types", childNodesUrl: url, id: 1237, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } - ]; + t = { + name: "settings", + id: -1, + children: [ + { name: "Stylesheets", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Templates", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Dictionary", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Media types", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "Document types", childNodesUrl: url, id: -1, icon: "icon-folder-close", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + ], + expanded: true, + hasChildren: true, + level: 0, + isContainer: true + }; + break; default: - t = [ - { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", defaultAction: "create", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, - { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } - ]; + + t = { + name: "randomTree", + id: -1, + children: [ + { name: "random-name-" + section, childNodesUrl: url, id: 1234, icon: "icon-home", defaultAction: "create", view: section + "/edit/" + 1234, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1235, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1235, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1236, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1236, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl }, + { name: "random-name-" + section, childNodesUrl: url, id: 1237, icon: "icon-folder-close", defaultAction: "create", view: section + "/edit/" + 1237, children: [], expanded: false, hasChildren: true, level: 1, menuUrl: menuUrl } + ], + expanded: true, + hasChildren: true, + level: 0, + menuUrl: menuUrl + }; + break; } 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 3f2a879ec8..99083e6cd5 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 @@ -50,10 +50,10 @@ function treeService($q, treeResource, iconHelper) { var result = { name: section, alias: section, - children: data + root: data }; //ensure the view is added to each tree node - ensureParentLevelAndView(null, result.children, section); + ensureParentLevelAndView(result.root, result.root.children, section); //cache this result //TODO: We'll need to un-cache this in many circumstances treeArray[cacheKey] = result; diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 19ecf23422..f18e33637a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -124,6 +124,10 @@ i.umb-options i { margin: 10px -2px 0 0; } +li.root > div > i.umb-options { + top: 10px; +} + .hide-options .umb-options{display: none !important} .hide-header h5{display: none !important} diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html index 11910cfbfa..17362da623 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html @@ -1,4 +1,4 @@ -
+
    diff --git a/src/Umbraco.Web/HttpUrlHelperExtensions.cs b/src/Umbraco.Web/HttpUrlHelperExtensions.cs index 79a7b3e583..829bc419c7 100644 --- a/src/Umbraco.Web/HttpUrlHelperExtensions.cs +++ b/src/Umbraco.Web/HttpUrlHelperExtensions.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web //set the area to the plugin area area = metaData.AreaName; } - return url.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area); + return url.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, id); } /// diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 7dabf8f709..0b5cd3eb5a 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Management.Instrumentation; using System.Net.Http.Formatting; @@ -34,16 +35,25 @@ namespace Umbraco.Web.Trees /// /// [HttpQueryStringFilter("queryStrings")] - public TreeNodeCollection GetApplicationTrees(string application, FormDataCollection queryStrings) + public SectionRootNode GetApplicationTrees(string application, FormDataCollection queryStrings) { if (application == null) throw new ArgumentNullException("application"); + var rootId = Core.Constants.System.Root.ToString(CultureInfo.InvariantCulture); + //find all tree definitions that have the current application alias var appTrees = ApplicationContext.Current.Services.ApplicationTreeService.GetApplicationTrees(application).Where(x => x.Initialize).ToArray(); if (appTrees.Count() == 1) - { - //return the nodes for the one tree assigned - return GetNodeCollection(appTrees.Single(), "-1", queryStrings); + { + return new SectionRootNode( + rootId, + Url.GetUmbracoApiService("GetMenu", rootId) + + "&parentId=" + rootId + + "&treeType=" + application + + "§ion=" + application) + { + Children = GetNodeCollection(appTrees.Single(), "-1", queryStrings) + }; } var collection = new TreeNodeCollection(); @@ -53,7 +63,12 @@ namespace Umbraco.Web.Trees var rootNode = GetRoot(tree, queryStrings); collection.Add(rootNode); } - return collection; + + return new SectionRootNode(rootId, "") + { + Children = collection, + IsContainer = true + }; } ///// diff --git a/src/Umbraco.Web/Trees/SectionRootNode.cs b/src/Umbraco.Web/Trees/SectionRootNode.cs new file mode 100644 index 0000000000..2bee0e98c7 --- /dev/null +++ b/src/Umbraco.Web/Trees/SectionRootNode.cs @@ -0,0 +1,30 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Trees +{ + /// + /// A special tree node that represents the section root node for any section. + /// + /// + /// This is required to return the tree data for a given section. Some sections may only contain one tree which means it's section + /// root should also display a menu, whereas other sections have multiple trees and the section root shouldn't display a menu. + /// + /// The section root also contains an explicit collection of children. + /// + [DataContract(Name = "node", Namespace = "")] + public sealed class SectionRootNode : TreeNode + { + public SectionRootNode(string nodeId, string menuUrl) + : base(nodeId, string.Empty, menuUrl) + { + //default to false + IsContainer = false; + } + + [DataMember(Name = "isContainer")] + public bool IsContainer { get; set; } + + [DataMember(Name = "children")] + public TreeNodeCollection Children { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/TreeNode.cs b/src/Umbraco.Web/Trees/TreeNode.cs index b44f71a139..aee383e97d 100644 --- a/src/Umbraco.Web/Trees/TreeNode.cs +++ b/src/Umbraco.Web/Trees/TreeNode.cs @@ -2,6 +2,7 @@ using System.Runtime.Serialization; using Newtonsoft.Json; using Umbraco.Core.IO; +using Umbraco.Core; namespace Umbraco.Web.Trees { @@ -11,8 +12,7 @@ namespace Umbraco.Web.Trees [DataContract(Name = "node", Namespace = "")] public class TreeNode { - //private readonly List _menuItems = new List(); - + public TreeNode(string nodeId, string getChildNodesUrl, string menuUrl) { //_menuItems = menuItems; @@ -23,15 +23,6 @@ namespace Umbraco.Web.Trees MenuUrl = menuUrl; } - /// - /// Internal child array to be able to add nested levels of content - /// - /// - /// Currently this is ONLY supported for rendering the root node for an application - /// tree when there are multiple trees for an application - /// - internal IEnumerable Children { get; set; } - /// /// The unique identifier for the node /// @@ -78,6 +69,10 @@ namespace Umbraco.Web.Trees { get { + if (Icon.IsNullOrWhiteSpace()) + { + return true; + } //if it starts with a '.' or doesn't contain a '.' at all then it is a class return Icon.StartsWith(".") || Icon.Contains(".") == false; } diff --git a/src/Umbraco.Web/Trees/TreeNodeCollection.cs b/src/Umbraco.Web/Trees/TreeNodeCollection.cs index 41f2caed2c..f4d65bb5e6 100644 --- a/src/Umbraco.Web/Trees/TreeNodeCollection.cs +++ b/src/Umbraco.Web/Trees/TreeNodeCollection.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json.Linq; namespace Umbraco.Web.Trees { [CollectionDataContract(Name = "nodes", Namespace = "")] - public class TreeNodeCollection : List - { + public sealed class TreeNodeCollection : List + { } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0a31c6ff32..1116a4b9b1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -336,6 +336,7 @@ +