From 38c1669e57af7e73a063e144da2d8091aef9d376 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 12 Dec 2013 13:27:33 +1100 Subject: [PATCH] Gets content items that are children of listviews to query for their affiliated tree-node and display the action menu. --- .../src/common/services/tree.service.js | 8 +- .../views/content/content.edit.controller.js | 36 ++++-- src/Umbraco.Web/Editors/ContentController.cs | 13 +++ .../ContentEditing/ContentItemDisplay.cs | 14 ++- .../Models/Mapping/ContentModelMapper.cs | 41 +++++-- .../Trees/ContentTreeController.cs | 107 +++++++++++++----- 6 files changed, 168 insertions(+), 51 deletions(-) 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 63c77a114a..d291ace2ec 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 @@ -407,9 +407,15 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS if (current.metaData && current.metaData["treeAlias"]) { root = current; } - else { + else if (angular.isFunction(current.parent)) { + //we can only continue if there is a parent() method which means this + // tree node was loaded in as part of a real tree, not just as a single tree + // node from the server. current = current.parent(); } + else { + current = null; + } } return root; }, diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 27d2614330..c990f19a03 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -6,7 +6,7 @@ * @description * The controller for the content editor */ -function ContentEditController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState) { +function ContentEditController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) { //setup scope vars $scope.defaultButton = null; @@ -107,6 +107,26 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS } } + /** Syncs the content item to it's tree node - this occurs on first load and after saving */ + function syncTreeNode(content, path, initialLoad) { + + //If this is a child of a list view then we can't actually sync the real tree + if (!$scope.content.isChildOfListView) { + navigationService.syncTree({ tree: "content", path: path.split(","), forceReload: true }).then(function (syncArgs) { + $scope.currentNode = syncArgs.node; + }); + } + else if (initialLoad === true) { + //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node + // from the server so that we can load in the actions menu. + umbRequestHelper.resourcePromise( + $http.get(content.treeNodeUrl), + 'Failed to retreive data for child node ' + content.id).then(function(node) { + $scope.currentNode = node; + }); + } + } + /** This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish */ function performSave(args) { var deferred = $q.defer(); @@ -131,9 +151,7 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS configureButtons(data); - navigationService.syncTree({ tree: "content", path: data.path.split(","), forceReload: true }).then(function (syncArgs) { - $scope.currentNode = syncArgs.node; - }); + syncTreeNode($scope.content, data.path); deferred.resolve(data); @@ -188,9 +206,8 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS // if there are any and then clear them so the collection no longer persists them. serverValidationManager.executeAndClearAllSubscriptions(); - navigationService.syncTree({ tree: "content", path: data.path.split(",") }).then(function(syncArgs) { - $scope.currentNode = syncArgs.node; - }); + syncTreeNode($scope.content, data.path, true); + }); } @@ -214,9 +231,8 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS configureButtons(data); - navigationService.syncTree({ tree: "content", path: data.path.split(","), forceReload: true }).then(function (syncArgs) { - $scope.currentNode = syncArgs.node; - }); + syncTreeNode($scope.content, data.path); + }); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index f51aa7aa79..63961782a2 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -90,6 +90,19 @@ namespace Umbraco.Web.Editors return content; } + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetWithTreeDefinition(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = Mapper.Map(foundContent); + return content; + } + /// /// Gets an empty content item for the /// diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 6994fe7312..8410c140ba 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http.Formatting; using System.Runtime.Serialization; using System.Web.Http; using System.Web.Http.ModelBinding; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Validation; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Trees; namespace Umbraco.Web.Models.ContentEditing { + /// /// A model representing a content item to be displayed in the back office /// @@ -29,7 +34,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "urls")] public string[] Urls { get; set; } - + /// /// The allowed 'actions' based on the user's permissions - Create, Update, Publish, Send to publish /// @@ -38,5 +43,12 @@ namespace Umbraco.Web.Models.ContentEditing /// [DataMember(Name = "allowedActions")] public IEnumerable AllowedActions { get; set; } + + [DataMember(Name = "isChildOfListView")] + public bool IsChildOfListView { get; set; } + + [DataMember(Name = "treeNodeUrl")] + public string TreeNodeUrl { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index d1bd00d2f3..38b43f9cfb 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -4,6 +4,9 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Runtime.Serialization; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; using AutoMapper; using Newtonsoft.Json; using Umbraco.Core; @@ -12,6 +15,7 @@ using Umbraco.Core.Models.Mapping; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; using umbraco; using Umbraco.Web.Routing; using umbraco.BusinessLogic.Actions; @@ -25,10 +29,10 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - + //FROM IContent TO ContentItemDisplay config.CreateMap() - .ForMember( + .ForMember( dto => dto.Owner, expression => expression.ResolveUsing>()) .ForMember( @@ -43,6 +47,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember( dto => dto.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) + .ForMember( + dto => dto.IsChildOfListView, + expression => expression.MapFrom(content => content.Parent().ContentType.IsContainer)) .ForMember( dto => dto.PublishDate, expression => expression.MapFrom(content => GetPublishedDate(content, applicationContext))) @@ -58,7 +65,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Tabs, expression => expression.ResolveUsing()) .ForMember(display => display.AllowedActions, expression => expression.ResolveUsing( new ActionButtonsResolver(new Lazy(() => applicationContext.Services.UserService)))) - .AfterMap(MapGenericCustomProperties); + .AfterMap(AfterMap); //FROM IContent TO ContentItemBasic config.CreateMap>() @@ -81,7 +88,25 @@ namespace Umbraco.Web.Models.Mapping dto => dto.Owner, expression => expression.ResolveUsing>()); - + + } + + private static void AfterMap(IContent content, ContentItemDisplay display) + { + MapGenericCustomProperties(content, display); + MapTreeNodeUrl(content, display); + } + + private static void MapTreeNodeUrl(IContent content, ContentItemDisplay display) + { + if (HttpContext.Current == null) + { + return; + } + + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); + display.TreeNodeUrl = url; } /// @@ -92,12 +117,12 @@ namespace Umbraco.Web.Models.Mapping private static void MapGenericCustomProperties(IContent content, ContentItemDisplay display) { //fill in the template config to be passed to the template drop down. - var templateItemConfig = new Dictionary {{"", "Choose..."}}; + var templateItemConfig = new Dictionary { { "", "Choose..." } }; foreach (var t in content.ContentType.AllowedTemplates) { templateItemConfig.Add(t.Alias, t.Name); } - + if (content.ContentType.IsContainer) { TabsAndPropertiesResolver.AddContainerView(display, "content"); @@ -139,7 +164,7 @@ namespace Umbraco.Web.Models.Mapping }); } - + /// /// Gets the published date value for the IContent object @@ -214,7 +239,7 @@ namespace Umbraco.Web.Models.Mapping return result; } - } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 51c4f296dc..4ef3c4724b 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Http.Formatting; using System.Web.Http; using Umbraco.Core; @@ -36,6 +37,32 @@ namespace Umbraco.Web.Trees [CoreTree] public class ContentTreeController : ContentTreeControllerBase { + #region Actions + + [HttpQueryStringFilter("queryStrings")] + public TreeNode GetTreeNode(string id, FormDataCollection queryStrings) + { + int asInt; + if (int.TryParse(id, out asInt) == false) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + + var entity = Services.EntityService.Get(asInt, UmbracoObjectTypes.Document); + if (entity == null) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + + var node = GetContentTreeNode((UmbracoEntity) entity, entity.ParentId.ToInvariantString(), queryStrings); + + //add the tree alias to the node since it is standalone (has no root for which this normally belongs) + node.AdditionalData["treeAlias"] = TreeAlias; + return node; + } + + #endregion + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var node = base.CreateRootNode(queryStrings); @@ -81,44 +108,62 @@ namespace Umbraco.Web.Trees foreach (var entity in entities) { var e = (UmbracoEntity)entity; - - var allowedUserOptions = GetAllowedUserMenuItemsForNode(e); - if (CanUserAccessNode(e, allowedUserOptions)) - { - //Special check to see if it ia a container, if so then we'll hide children. - var isContainer = entity.AdditionalData.ContainsKey("IsContainer") - && entity.AdditionalData["IsContainer"] is bool - && (bool) entity.AdditionalData["IsContainer"]; - - var node = CreateTreeNode( - e.Id.ToInvariantString(), - id, - queryStrings, - e.Name, - e.ContentTypeIcon, - e.HasChildren && (isContainer == false)); - - node.AdditionalData.Add("contentType", e.ContentTypeAlias); - - if (isContainer) - node.SetContainerStyle(); - - if (e.IsPublished == false) - node.SetNotPublishedStyle(); - - if (e.HasPendingChanges) - node.SetHasUnpublishedVersionStyle(); - - if (Access.IsProtected(e.Id, e.Path)) - node.SetProtectedStyle(); - + var node = GetContentTreeNode(e, id, queryStrings); + if (node != null) + { nodes.Add(node); } } return nodes; } + /// + /// Creates a tree node for a content item based on an UmbracoEntity + /// + /// + /// + /// + /// + internal TreeNode GetContentTreeNode(UmbracoEntity e, string parentId, FormDataCollection queryStrings) + { + var allowedUserOptions = GetAllowedUserMenuItemsForNode(e); + if (CanUserAccessNode(e, allowedUserOptions)) + { + + //Special check to see if it ia a container, if so then we'll hide children. + var isContainer = e.AdditionalData.ContainsKey("IsContainer") + && e.AdditionalData["IsContainer"] is bool + && (bool)e.AdditionalData["IsContainer"]; + + var node = CreateTreeNode( + e.Id.ToInvariantString(), + parentId, + queryStrings, + e.Name, + e.ContentTypeIcon, + e.HasChildren && (isContainer == false)); + + node.AdditionalData.Add("contentType", e.ContentTypeAlias); + + if (isContainer) + node.SetContainerStyle(); + + if (e.IsPublished == false) + node.SetNotPublishedStyle(); + + if (e.HasPendingChanges) + node.SetHasUnpublishedVersionStyle(); + + if (Access.IsProtected(e.Id, e.Path)) + node.SetProtectedStyle(); + + return node; + } + + return null; + } + protected override MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings) { if (id == Constants.System.Root.ToInvariantString())