From 5222cfcfe77048058382bdc8281107797c20802d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 11 Jul 2013 19:46:13 +1000 Subject: [PATCH] Got pretty far with deletions. It still doesn't actually do the deletions but the dialog is in place, now just need to inject some more metadata into the menu item to get the legacy delete controller to call the correct rest method to delete the legacy items (again, this is based on the silly ui.xml stuff) --- src/Umbraco.Core/Attempt.cs | 70 +++++++++++++++++++ src/Umbraco.Tests/AttemptTests.cs | 32 +++++++++ src/Umbraco.Tests/GlobalSettingsTests.cs | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../common/directives/umbconfirm.directive.js | 25 +++++++ .../src/common/services/navigation.service.js | 4 ++ .../common/dialogs/legacydelete.controller.js | 22 ++++++ .../views/common/dialogs/legacydelete.html | 8 +++ .../src/views/common/navigation.controller.js | 16 +++-- .../src/views/directives/umb-confirm.html | 13 ++++ src/Umbraco.Web/Trees/ActionUrlMethod.cs | 11 +++ .../Trees/LegacyTreeDataConverter.cs | 58 +++++++++++---- src/Umbraco.Web/Trees/MenuItem.cs | 42 ----------- src/Umbraco.Web/Trees/MenuItemExtensions.cs | 35 ++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 15 files changed, 280 insertions(+), 62 deletions(-) create mode 100644 src/Umbraco.Tests/AttemptTests.cs create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/umbconfirm.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.controller.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.html create mode 100644 src/Umbraco.Web.UI.Client/src/views/directives/umb-confirm.html create mode 100644 src/Umbraco.Web/Trees/ActionUrlMethod.cs create mode 100644 src/Umbraco.Web/Trees/MenuItemExtensions.cs diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index c184c5670a..6054056645 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -2,6 +2,55 @@ using System; namespace Umbraco.Core { + public struct AttemptOutcome + { + private readonly bool _success; + + public AttemptOutcome(bool success) + { + _success = success; + } + + public AttemptOutcome IfFailed(Func> nextAttempt, Action onSuccess, Action onFail = null) + { + if (_success == false) + { + return ExecuteNextAttempt(nextAttempt, onSuccess, onFail); + } + + //return a successful outcome since the last one was successful, this allows the next AttemptOutcome chained to + // continue properly. + return new AttemptOutcome(true); + } + + public AttemptOutcome IfSuccessful(Func> nextAttempt, Action onSuccess, Action onFail = null) + { + if (_success) + { + return ExecuteNextAttempt(nextAttempt, onSuccess, onFail); + } + //return a failed outcome since the last one was not successful, this allows the next AttemptOutcome chained to + // continue properly. + return new AttemptOutcome(false); + } + + private AttemptOutcome ExecuteNextAttempt(Func> nextAttempt, Action onSuccess, Action onFail = null) + { + var attempt = nextAttempt(); + if (attempt.Success) + { + onSuccess(attempt.Result); + return new AttemptOutcome(true); + } + + if (onFail != null) + { + onFail(attempt.Error); + } + return new AttemptOutcome(false); + } + } + /// /// Represents the result of an operation attempt /// @@ -38,6 +87,27 @@ namespace Umbraco.Core get { return _result; } } + /// + /// Perform the attempt with callbacks + /// + /// + /// + /// + public static AttemptOutcome Try(Attempt attempt, Action onSuccess, Action onFail = null) + { + if (attempt.Success) + { + onSuccess(attempt.Result); + return new AttemptOutcome(true); + } + + if (onFail != null) + { + onFail(attempt.Error); + } + return new AttemptOutcome(false); + } + /// /// Represents an unsuccessful parse operation /// diff --git a/src/Umbraco.Tests/AttemptTests.cs b/src/Umbraco.Tests/AttemptTests.cs new file mode 100644 index 0000000000..cb775a3b2e --- /dev/null +++ b/src/Umbraco.Tests/AttemptTests.cs @@ -0,0 +1,32 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests +{ + [TestFixture] + public class AttemptTests + { + + [Test] + public void Chained_Attempts() + { + Attempt.Try(new Attempt(true, "success!"), + s => Assert.AreEqual("success!", s), + exception => Assert.Fail("It was successful")) + .IfFailed(() => new Attempt(true, 123), + i => Assert.Fail("The previous attempt was successful!"), + exception => Assert.Fail("The previous attempt was successful!")) + .IfSuccessful(() => new Attempt(new Exception("Failed!")), + d => Assert.Fail("An exception was thrown"), + exception => Assert.AreEqual("Failed!", exception.Message)) + .IfSuccessful(() => new Attempt(true, 987), + i => Assert.Fail("The previous attempt failed!"), + exception => Assert.Fail("The previous attempt failed!")) + .IfFailed(() => new Attempt(true, "finished"), + i => Assert.AreEqual("finished", i), + exception => Assert.Fail("It was successful")); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/GlobalSettingsTests.cs b/src/Umbraco.Tests/GlobalSettingsTests.cs index fb1f14f72b..b4f6d9dd2a 100644 --- a/src/Umbraco.Tests/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests/GlobalSettingsTests.cs @@ -8,7 +8,7 @@ using System.Web.Mvc; namespace Umbraco.Tests { - [TestFixture] + [TestFixture] public class GlobalSettingsTests : BaseWebTest { protected override DatabaseBehavior DatabaseTestBehavior diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index aff8c5b814..4a7abc4054 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -152,6 +152,7 @@ + @@ -198,7 +199,6 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbconfirm.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbconfirm.directive.js new file mode 100644 index 0000000000..8f5085741c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbconfirm.directive.js @@ -0,0 +1,25 @@ +/** + * @ngdoc directive + * @name umbraco.directives:umbConfirm + * + * @description + * A confirmation dialog + * + * @restrict E + */ +function confirmDirective() { + return { + restrict: "E", // restrict to an element + replace: true, // replace the html element with the template + templateUrl: 'views/directives/umb-confirm.html', + scope: { + onConfirm: '=', + onCancel: '=', + caption: '@' + }, + link: function (scope, element, attr, ctrl) { + + } + }; +} +angular.module('umbraco.directives').directive("umbConfirm", confirmDirective); \ 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 c72d926ead..00438c326e 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 @@ -270,6 +270,10 @@ angular.module('umbraco.services') templateUrl = args.action.metaData["actionUrl"]; iframe = true; } + else if (args.action.view) { + templateUrl = args.action.view; + iframe = false; + } else { templateUrl = "views/" + this.ui.currentTree + "/" + args.action.alias + ".html"; iframe = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.controller.js new file mode 100644 index 0000000000..1b875e2c78 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.controller.js @@ -0,0 +1,22 @@ +/** + * @ngdoc controller + * @name LegacyDeleteController + * @function + * + * @description + * The controller for deleting content + */ +function LegacyDeleteController($scope) { + + $scope.caption = "Are you sure you want to delete the node '" + $scope.currentNode.name + "' ?"; + + $scope.performDelete = function() { + alert("Deleted!"); + }; + + $scope.cancel = function() { + $scope.hideDialog(); + }; +} + +angular.module("umbraco").controller("Umbraco.Dialogs.LegacyDeleteController", LegacyDeleteController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.html new file mode 100644 index 0000000000..9abdecfbfc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/legacydelete.html @@ -0,0 +1,8 @@ +
+
+ + + + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js index 9b7d50f89d..bebc1055bd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/navigation.controller.js @@ -24,6 +24,8 @@ function NavigationController($scope,$rootScope, $location, navigationService, d $scope.hideDialog = navigationService.hideDialog; $scope.hideNavigation = navigationService.hideNavigation; $scope.ui = navigationService.ui; + //track the currently opened dialog + $scope.ui.currentDialog = null; //tree event handler everyone can subscribe to $scope.ui.tree = $({}); @@ -56,12 +58,12 @@ function NavigationController($scope,$rootScope, $location, navigationService, d args.scope = $scope; //ensure the menuDialog is cleared before opening another! - if (menuDialog) { - dialogService.close(menuDialog); + if ($scope.ui.currentDialog) { + dialogService.close($scope.ui.currentDialog); } navigationService.showMenu(ev, args) .then(function(result) { - menuDialog = result; + $scope.ui.currentDialog = result; }); }); @@ -103,12 +105,14 @@ function NavigationController($scope,$rootScope, $location, navigationService, d $scope.openDialog = function (currentNode, action, currentSection) { + $scope.currentAction = action; + //ensure the actionDialog is cleared before opening another! - if (actionDialog) { - dialogService.close(actionDialog); + if ($scope.ui.currentDialog) { + dialogService.close($scope.ui.currentDialog); } - actionDialog = navigationService.showDialog({ + $scope.ui.currentDialog = navigationService.showDialog({ scope: $scope, node: currentNode, action: action, diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-confirm.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-confirm.html new file mode 100644 index 0000000000..ab38cc22a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-confirm.html @@ -0,0 +1,13 @@ +
+

{{caption}}

+ + + +
\ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ActionUrlMethod.cs b/src/Umbraco.Web/Trees/ActionUrlMethod.cs new file mode 100644 index 0000000000..cf7c0e3399 --- /dev/null +++ b/src/Umbraco.Web/Trees/ActionUrlMethod.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Web.Trees +{ + /// + /// Specifies the action to take for a menu item when a URL is specified + /// + public enum ActionUrlMethod + { + Dialog, + BlankWindow + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 5932767d8b..054b3495a2 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Web; using System.Web.Http.Routing; using Umbraco.Core; +using Umbraco.Core.IO; using umbraco; using umbraco.BusinessLogic.Actions; using umbraco.cms.helpers; @@ -59,13 +61,20 @@ namespace Umbraco.Web.Trees { var menuItem = collection.AddMenuItem(t); - //Now we need to figure out how to deal with the legacy menu actions t.JsSource - var tryGetLegacyUrl = GetUrlAndTitleFromLegacyAction(t, xmlTreeNode, currentSection); - if (tryGetLegacyUrl.Success) - { - menuItem.SetActionUrl(tryGetLegacyUrl.Result.Url, tryGetLegacyUrl.Result.ActionMethod); - menuItem.SetDialogTitle(tryGetLegacyUrl.Result.DialogTitle); - } + var currentAction = t; + + //First try to get a URL/title from the legacy action, + // if that doesn't work, try to get the legacy confirm view + Attempt.Try(GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode, currentSection), action => + { + menuItem.SetActionUrl(action.Url, action.ActionMethod); + menuItem.SetDialogTitle(action.DialogTitle); + }) + .IfFailed(() => GetLegacyConfirmView(currentAction, xmlTreeNode, currentSection), view => + { + menuItem.View = view; + }); + numAdded++; } } @@ -81,6 +90,31 @@ namespace Umbraco.Web.Trees return collection; } + /// + /// This will look at the legacy IAction's JsFunctionName and convert it to a confirmation dialog view if possible + /// + /// + /// + /// + /// + internal static Attempt GetLegacyConfirmView(IAction action, XmlTreeNode actionNode, string currentSection) + { + if (action.JsFunctionName.IsNullOrWhiteSpace()) + { + return Attempt.False; + } + + switch (action.JsFunctionName) + { + case "UmbClientMgr.appActions().actionDelete()": + return new Attempt( + true, + Core.Configuration.GlobalSettings.Path.EnsureEndsWith('/') + "views/common/dialogs/legacydelete.html"); + } + + return Attempt.False; + } + /// /// This will look at a legacy IAction's JsFunctionName and convert it to a URL if possible. /// @@ -212,7 +246,7 @@ namespace Umbraco.Web.Trees "dialogs/moveOrCopy.aspx?app=" + currentSection + "&mode=copy&id=" + actionNode.NodeID + "&rnd=" + DateTime.UtcNow.Ticks, ui.GetText("actions", "copy"))); } - return Attempt.False; + return Attempt.False; } internal static TreeNode ConvertFromLegacy(string parentId, XmlTreeNode xmlTreeNode, UrlHelper urlHelper, string currentSection, bool isRoot = false) @@ -225,7 +259,7 @@ namespace Umbraco.Web.Trees var childQuery = (xmlTreeNode.Source.IsNullOrWhiteSpace() || xmlTreeNode.Source.IndexOf('?') == -1) ? "" : xmlTreeNode.Source.Substring(xmlTreeNode.Source.IndexOf('?')); - + //append the query strings childNodesSource = childNodesSource.AppendQueryStringToUrl(childQuery); @@ -248,13 +282,13 @@ namespace Umbraco.Web.Trees Icon = xmlTreeNode.Icon, Title = xmlTreeNode.Text, NodeType = xmlTreeNode.NodeType - + }; //This is a special case scenario, we know that content/media works based on the normal Belle routing/editing so we'll ensure we don't // pass in the legacy JS handler so we do it the new way, for all other trees (Currently, this is a WIP), we'll render // the legacy js callback,. - var knownNonLegacyNodeTypes = new[] {"content", "contentRecycleBin", "mediaRecyleBin", "media"}; + var knownNonLegacyNodeTypes = new[] { "content", "contentRecycleBin", "mediaRecyleBin", "media" }; if (knownNonLegacyNodeTypes.InvariantContains(xmlTreeNode.NodeType) == false) { node.OnClickCallback = xmlTreeNode.Action; @@ -280,7 +314,7 @@ namespace Umbraco.Web.Trees public LegacyUrlAction(string url, string dialogTitle) : this(url, dialogTitle, ActionUrlMethod.Dialog) { - + } public LegacyUrlAction(string url, string dialogTitle, ActionUrlMethod actionMethod) diff --git a/src/Umbraco.Web/Trees/MenuItem.cs b/src/Umbraco.Web/Trees/MenuItem.cs index 30c3d9d321..3f8cdabe44 100644 --- a/src/Umbraco.Web/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Trees/MenuItem.cs @@ -55,46 +55,4 @@ namespace Umbraco.Web.Trees } - - public static class MenuItemExtensions - { - /// - /// Used as a key for the AdditionalData to specify a specific dialog title instead of the menu title - /// - internal const string DialogTitleKey = "dialogTitle"; - internal const string ActionUrlKey = "actionUrl"; - internal const string ActionUrlMethodKey = "actionUrlMethod"; - - /// - /// Puts a dialog title into the meta data to be displayed on the dialog of the menu item (if there is one) - /// instead of the menu name - /// - /// - /// - public static void SetDialogTitle(this MenuItem menuItem, string dialogTitle) - { - menuItem.AdditionalData[DialogTitleKey] = dialogTitle; - } - - /// - /// Configures the menu item to launch a URL with the specified action (dialog or new window) - /// - /// - /// - /// - public static void SetActionUrl(this MenuItem menuItem, string url, ActionUrlMethod method = ActionUrlMethod.Dialog) - { - menuItem.AdditionalData[ActionUrlKey] = url; - menuItem.AdditionalData[ActionUrlMethodKey] = url; - } - } - - /// - /// Specifies the action to take for a menu item when a URL is specified - /// - public enum ActionUrlMethod - { - Dialog, - BlankWindow - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/MenuItemExtensions.cs b/src/Umbraco.Web/Trees/MenuItemExtensions.cs new file mode 100644 index 0000000000..dcb82fcf0e --- /dev/null +++ b/src/Umbraco.Web/Trees/MenuItemExtensions.cs @@ -0,0 +1,35 @@ +namespace Umbraco.Web.Trees +{ + public static class MenuItemExtensions + { + /// + /// Used as a key for the AdditionalData to specify a specific dialog title instead of the menu title + /// + internal const string DialogTitleKey = "dialogTitle"; + internal const string ActionUrlKey = "actionUrl"; + internal const string ActionUrlMethodKey = "actionUrlMethod"; + + /// + /// Puts a dialog title into the meta data to be displayed on the dialog of the menu item (if there is one) + /// instead of the menu name + /// + /// + /// + public static void SetDialogTitle(this MenuItem menuItem, string dialogTitle) + { + menuItem.AdditionalData[DialogTitleKey] = dialogTitle; + } + + /// + /// Configures the menu item to launch a URL with the specified action (dialog or new window) + /// + /// + /// + /// + public static void SetActionUrl(this MenuItem menuItem, string url, ActionUrlMethod method = ActionUrlMethod.Dialog) + { + menuItem.AdditionalData[ActionUrlKey] = url; + menuItem.AdditionalData[ActionUrlMethodKey] = url; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b48e25a4ee..a2811d7c5f 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -323,6 +323,7 @@ + @@ -330,6 +331,7 @@ +