Implements: U4-2937 A tree item should be able to have a default action

This commit is contained in:
Shannon
2013-09-26 15:55:38 +10:00
parent c9fd168cbd
commit e1cf3bc597
14 changed files with 477 additions and 493 deletions

View File

@@ -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,

View File

@@ -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;
},

View File

@@ -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 })

View File

@@ -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,

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Web
internal static IEnumerable<Type> 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<TreeApiController, TreeAttribute>(cacheResult: false);
return resolver.ResolveTypesWithAttribute<TreeController, TreeAttribute>(cacheResult: false);
}
internal static IEnumerable<Type> ResolveSurfaceControllers(this PluginManager resolver)

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Web.Trees
{
//get reference to all TreeApiControllers
var controllerTrees = UmbracoApiControllerResolver.Current.RegisteredUmbracoApiControllers
.Where(TypeHelper.IsTypeAssignableFrom<TreeApiController>)
.Where(TypeHelper.IsTypeAssignableFrom<TreeController>)
.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

View File

@@ -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<ActionSort>();
//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<ActionRePublish>().ConvertLegacyMenuItem(null, "content", "content");
allowedMenu.AddMenuItem<RefreshNode, ActionRefresh>(true);
return allowedMenu;
menu.AddMenuItem<ActionRePublish>().ConvertLegacyMenuItem(null, "content", "content");
menu.AddMenuItem<RefreshNode, ActionRefresh>(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<MenuItem> CreateAllowedActions(IUmbracoEntity item)
/// <summary>
/// Returns a collection of all menu items that can be on a content node
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected MenuItemCollection GetAllNodeMenuItems(IUmbracoEntity item)
{
var menu = new MenuItemCollection();
menu.AddMenuItem<ActionNew>();
@@ -185,23 +195,5 @@ namespace Umbraco.Web.Trees
return menu;
}
///// <summary>
///// This is required so that the legacy tree dialog pickers and the legacy TreeControl.ascx stuff works with these new trees.
///// </summary>
///// <param name="node"></param>
///// <param name="queryStrings"></param>
///// <remarks>
///// 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
///// </remarks>
//private void InjectLegacyTreeCompatibility(TreeNode node, FormDataCollection queryStrings)
//{
// if (IsDialog(queryStrings))
// {
// node.AdditionalData["legacyDialogAction"] = "javascript:openContent(" + node.NodeId + ");";
// }
//}
}
}

View File

@@ -11,7 +11,7 @@ using umbraco.BusinessLogic.Actions;
namespace Umbraco.Web.Trees
{
public abstract class ContentTreeControllerBase : TreeApiController
public abstract class ContentTreeControllerBase : TreeController
{
/// <summary>
/// Returns the
@@ -99,19 +99,27 @@ namespace Umbraco.Web.Trees
/// <summary>
/// Based on the allowed actions, this will filter the ones that the current user is allowed
/// </summary>
/// <param name="allMenuItems"></param>
/// <param name="menuWithAllItems"></param>
/// <param name="userAllowedMenuItems"></param>
/// <returns></returns>
protected MenuItemCollection GetUserAllowedMenuItems(IEnumerable<MenuItem> allMenuItems, IEnumerable<MenuItem> userAllowedMenuItems)
protected void FilterUserAllowedMenuItems(MenuItemCollection menuWithAllItems, IEnumerable<MenuItem> 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<MenuItem> 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));
}
/// <summary>

View File

@@ -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)
{

View File

@@ -60,7 +60,7 @@ namespace Umbraco.Web.Trees
var legacyAtt = controllerAttempt.Result.GetCustomAttribute<LegacyBaseTreeAttribute>(false);
if (legacyAtt == null)
{
LogHelper.Warn<LegacyTreeDataConverter>("Cannot render tree: " + appTree.Alias + ". Cannot render a " + typeof(TreeApiController) + " tree type with the legacy web services unless attributed with " + typeof(LegacyBaseTreeAttribute));
LogHelper.Warn<LegacyTreeDataConverter>("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;
}
}

View File

@@ -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

View File

@@ -9,12 +9,14 @@ using umbraco.interfaces;
namespace Umbraco.Web.Trees.Menu
{
[CollectionDataContract(Name = "menuItems", Namespace = "")]
public class MenuItemCollection : IEnumerable<MenuItem>
[DataContract(Name = "menuItems", Namespace = "")]
public class MenuItemCollection
{
private readonly List<MenuItem> _menuItems;
public MenuItemCollection()
{
_menuItems = new List<MenuItem>();
}
public MenuItemCollection(IEnumerable<MenuItem> items)
@@ -22,7 +24,20 @@ namespace Umbraco.Web.Trees.Menu
_menuItems = new List<MenuItem>(items);
}
private readonly List<MenuItem> _menuItems = new List<MenuItem>();
/// <summary>
/// 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.
/// </summary>
[DataMember(Name = "defaultAlias")]
public string DefaultMenuAlias { get; set; }
/// <summary>
/// The list of menu items
/// </summary>
[DataMember(Name = "menuItems")]
public IEnumerable<MenuItem> MenuItems
{
get { return _menuItems; }
}
/// <summary>
/// Adds a menu item
@@ -37,6 +52,15 @@ namespace Umbraco.Web.Trees.Menu
return item;
}
/// <summary>
/// Removes a menu item
/// </summary>
/// <param name="item"></param>
public void RemoveMenuItem(MenuItem item)
{
_menuItems.Remove(item);
}
/// <summary>
/// Adds a menu item
/// </summary>
@@ -167,14 +191,5 @@ namespace Umbraco.Web.Trees.Menu
}
}
public IEnumerator<MenuItem> GetEnumerator()
{
return _menuItems.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -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
{
/// <summary>
/// The base controller for all tree requests
/// </summary>
public abstract class TreeApiController : UmbracoAuthorizedApiController
{
private readonly TreeAttribute _attribute;
/// <summary>
/// Remove the xml formatter... only support JSON!
/// </summary>
/// <param name="controllerContext"></param>
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<TreeAttribute>()
.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();
}
/// <summary>
/// The method called to render the contents of the tree structure
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings">
/// All of the query string parameters passed from jsTree
/// </param>
/// <remarks>
/// 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.
/// </remarks>
protected abstract TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings);
/// <summary>
/// Returns the menu structure for the node
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
protected abstract MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings);
/// <summary>
/// The name to display on the root node
/// </summary>
public virtual string RootNodeDisplayName
{
get { return _attribute.Title; }
}
/// <summary>
/// Gets the current tree alias from the attribute assigned to it.
/// </summary>
public string TreeAlias
{
get { return _attribute.Alias; }
}
/// <summary>
/// Returns the root node for the tree
/// </summary>
/// <param name="queryStrings"></param>
/// <returns></returns>
[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;
}
/// <summary>
/// The action called to render the contents of the tree structure
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings">
/// All of the query string parameters passed from jsTree
/// </param>
/// <returns>JSON markup for jsTree</returns>
/// <remarks>
/// 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.
/// </remarks>
[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;
}
/// <summary>
/// The action called to render the menu for a tree node
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
[HttpQueryStringFilter("queryStrings")]
public MenuItemCollection GetMenu(string id, FormDataCollection queryStrings)
{
if (queryStrings == null) queryStrings = new FormDataCollection("");
return GetMenuForNode(id, queryStrings);
}
/// <summary>
/// Helper method to create a root model for a tree
/// </summary>
/// <returns></returns>
protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings)
{
var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
var currApp = queryStrings.GetValue<string>(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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="node"></param>
/// <param name="queryStrings"></param>
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
/// <summary>
/// Helper method to create tree nodes
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="icon"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="routePath"></param>
/// <param name="icon"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes and automatically generate the json url
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="icon"></param>
/// <param name="hasChildren"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes and automatically generate the json url
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="routePath"></param>
/// <param name="hasChildren"></param>
/// <param name="icon"></param>
/// <returns></returns>
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
/// <summary>
/// If the request is for a dialog mode tree
/// </summary>
/// <param name="queryStrings"></param>
/// <returns></returns>
protected bool IsDialog(FormDataCollection queryStrings)
{
return queryStrings.GetValue<bool>(TreeQueryStringParameters.DialogMode);
}
#endregion
public static event EventHandler<TreeNodesRenderingEventArgs> TreeNodesRendering;
private static void OnTreeNodesRendering(TreeApiController instance, TreeNodesRenderingEventArgs e)
{
var handler = TreeNodesRendering;
if (handler != null) handler(instance, e);
}
public static event EventHandler<TreeNodeRenderingEventArgs> 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
{
/// <summary>
/// The base controller for all tree requests
/// </summary>
public abstract class TreeController : UmbracoAuthorizedApiController
{
private readonly TreeAttribute _attribute;
/// <summary>
/// Remove the xml formatter... only support JSON!
/// </summary>
/// <param name="controllerContext"></param>
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<TreeAttribute>()
.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();
}
/// <summary>
/// The method called to render the contents of the tree structure
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings">
/// All of the query string parameters passed from jsTree
/// </param>
/// <remarks>
/// 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.
/// </remarks>
protected abstract TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings);
/// <summary>
/// Returns the menu structure for the node
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
protected abstract MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings);
/// <summary>
/// The name to display on the root node
/// </summary>
public virtual string RootNodeDisplayName
{
get { return _attribute.Title; }
}
/// <summary>
/// Gets the current tree alias from the attribute assigned to it.
/// </summary>
public string TreeAlias
{
get { return _attribute.Alias; }
}
/// <summary>
/// Returns the root node for the tree
/// </summary>
/// <param name="queryStrings"></param>
/// <returns></returns>
[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;
}
/// <summary>
/// The action called to render the contents of the tree structure
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings">
/// All of the query string parameters passed from jsTree
/// </param>
/// <returns>JSON markup for jsTree</returns>
/// <remarks>
/// 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.
/// </remarks>
[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;
}
/// <summary>
/// The action called to render the menu for a tree node
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <returns></returns>
[HttpQueryStringFilter("queryStrings")]
public MenuItemCollection GetMenu(string id, FormDataCollection queryStrings)
{
if (queryStrings == null) queryStrings = new FormDataCollection("");
return GetMenuForNode(id, queryStrings);
}
/// <summary>
/// Helper method to create a root model for a tree
/// </summary>
/// <returns></returns>
protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings)
{
var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
var currApp = queryStrings.GetValue<string>(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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="node"></param>
/// <param name="queryStrings"></param>
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
/// <summary>
/// Helper method to create tree nodes
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="icon"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="routePath"></param>
/// <param name="icon"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes and automatically generate the json url
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="icon"></param>
/// <param name="hasChildren"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Helper method to create tree nodes and automatically generate the json url
/// </summary>
/// <param name="id"></param>
/// <param name="queryStrings"></param>
/// <param name="title"></param>
/// <param name="routePath"></param>
/// <param name="hasChildren"></param>
/// <param name="icon"></param>
/// <returns></returns>
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
/// <summary>
/// If the request is for a dialog mode tree
/// </summary>
/// <param name="queryStrings"></param>
/// <returns></returns>
protected bool IsDialog(FormDataCollection queryStrings)
{
return queryStrings.GetValue<bool>(TreeQueryStringParameters.DialogMode);
}
#endregion
public static event EventHandler<TreeNodesRenderingEventArgs> TreeNodesRendering;
private static void OnTreeNodesRendering(TreeController instance, TreeNodesRenderingEventArgs e)
{
var handler = TreeNodesRendering;
if (handler != null) handler(instance, e);
}
public static event EventHandler<TreeNodeRenderingEventArgs> RootNodeRendering;
private static void OnRootNodeRendering(TreeController instance, TreeNodeRenderingEventArgs e)
{
var handler = RootNodeRendering;
if (handler != null) handler(instance, e);
}
}
}

View File

@@ -428,7 +428,7 @@
<Compile Include="Trees\Menu\RefreshNode.cs" />
<Compile Include="Trees\SearchResultItem.cs" />
<Compile Include="Trees\SectionRootNode.cs" />
<Compile Include="Trees\TreeApiController.cs" />
<Compile Include="Trees\TreeController.cs" />
<Compile Include="Trees\ApplicationTreeExtensions.cs" />
<Compile Include="Trees\TreeAttribute.cs" />
<Compile Include="Trees\TreeNode.cs" />