using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Web.Trees; using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Web.Actions; using Umbraco.Web.Composing; namespace Umbraco.Web.Models.Trees { /// /// A context menu item /// [DataContract(Name = "menuItem", Namespace = "")] public class MenuItem { #region Constructors public MenuItem() { AdditionalData = new Dictionary(); Icon = "folder"; } public MenuItem(string alias, string name) : this() { Alias = alias; Name = name; } public MenuItem(string alias, ILocalizedTextService textService) : this() { Alias = alias; Name = textService.Localize($"actions/{Alias}"); } /// /// Create a menu item based on an definition /// /// /// public MenuItem(IAction action, string name = "") : this() { Name = name.IsNullOrWhiteSpace() ? action.Alias : name; Alias = action.Alias; SeparatorBefore = false; Icon = action.Icon; Action = action; } #endregion #region Properties internal IAction Action { get; set; } /// /// A dictionary to support any additional meta data that should be rendered for the node which is /// useful for custom action commands such as 'create', 'copy', etc... /// /// /// We will also use the meta data collection for dealing with legacy menu items (i.e. for loading custom URLs or /// executing custom JS). /// [DataMember(Name = "metaData")] public Dictionary AdditionalData { get; private set; } [DataMember(Name = "name", IsRequired = true)] [Required] public string Name { get; set; } [DataMember(Name = "alias", IsRequired = true)] [Required] public string Alias { get; set; } /// /// Ensures a menu separator will exist before this menu item /// [DataMember(Name = "separator")] public bool SeparatorBefore { get; set; } [DataMember(Name = "cssclass")] public string Icon { get; set; } /// /// Used in the UI to inform the user that the menu item will open a dialog/confirmation /// [DataMember(Name = "opensDialog")] public bool OpensDialog { get; set; } #endregion #region Constants /// /// Used as a key for the AdditionalData to specify a specific dialog title instead of the menu title /// internal const string DialogTitleKey = "dialogTitle"; /// /// Used to specify the URL that the dialog will launch to in an iframe /// internal const string ActionUrlKey = "actionUrl"; //TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, // though would be v-easy, just not sure we want to ever support that? internal const string ActionUrlMethodKey = "actionUrlMethod"; /// /// Used to specify the angular view that the dialog will launch /// internal const string ActionViewKey = "actionView"; /// /// Used to specify the js method to execute for the menu item /// internal const string JsActionKey = "jsAction"; /// /// Used to specify an angular route to go to for the menu item /// internal const string ActionRouteKey = "actionRoute"; #endregion #region Methods /// /// Sets the menu item to navigate to the specified angular route path /// /// public void NavigateToRoute(string route) { AdditionalData[ActionRouteKey] = route; } /// /// Adds the required meta data to the menu item so that angular knows to attempt to call the Js method. /// /// public void ExecuteJsMethod(string jsToExecute) { SetJsAction(jsToExecute); } /// /// Sets the menu item to display a dialog based on an angular view path /// /// /// public void LaunchDialogView(string view, string dialogTitle) { SetDialogTitle(dialogTitle); AdditionalData[ActionViewKey] = view; } /// /// Sets the menu item to display a dialog based on a url path in an iframe /// /// /// public void LaunchDialogUrl(string url, string dialogTitle) { SetDialogTitle(dialogTitle); SetActionUrl(url); } private void SetJsAction(string jsToExecute) { AdditionalData[JsActionKey] = jsToExecute; } /// /// 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 /// /// private void SetDialogTitle(string dialogTitle) { AdditionalData[DialogTitleKey] = dialogTitle; } /// /// Configures the menu item to launch a URL with the specified action (dialog or new window) /// /// /// private void SetActionUrl(string url, ActionUrlMethod method = ActionUrlMethod.Dialog) { AdditionalData[ActionUrlKey] = url; AdditionalData[ActionUrlMethodKey] = method; } internal void ConvertLegacyMenuItem(IUmbracoEntity item, string nodeType, string currentSection) { // try to get a URL/title from the legacy action, // in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. var attempt = LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, item == null ? "-1" : item.Id.ToInvariantString(), nodeType, item == null ? "" : item.Name, currentSection); if (attempt) { var action = attempt.Result; LaunchDialogUrl(action.Url, action.DialogTitle); } else { // if that doesn't work, try to get the legacy confirm view var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action); if (attempt2) { var view = attempt2.Result; var textService = Current.Services.TextService; LaunchDialogView(view, textService.Localize("defaultdialogs/confirmdelete") + " '" + (item == null ? "" : item.Name) + "' ?"); } } } #endregion } }