diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index ce4f6034ea..eb372eda56 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -56,8 +56,17 @@ namespace Umbraco.Core.Persistence.Repositories bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var factory = new UmbracoEntityFactory(); - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); + + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClaus in filter.GetWhereClauses()) + { + sql.Where(filterClaus.Item1, filterClaus.Item2); + } + } + }, objectTypeId); var translator = new SqlTranslator(sqlClause, query); var entitySql = translator.Translate(); var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)).OrderBy("umbracoNode.id"); @@ -141,7 +150,16 @@ namespace Umbraco.Core.Persistence.Repositories //the COUNT(*) query to return the wrong var sqlCountClause = GetBaseWhere( (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query - isContent, isMedia, null, objectTypeId); + isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClaus in filter.GetWhereClauses()) + { + sql.Where(filterClaus.Item1, filterClaus.Item2); + } + } + }, objectTypeId); var translatorCount = new SqlTranslator(sqlCountClause, query); var countSql = translatorCount.Translate(); diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 81d4cad6c0..b1744de429 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -85,6 +85,37 @@ namespace Umbraco.Tests.Services Assert.That(total, Is.EqualTo(60)); } + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants_With_Search() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, "ssss" + Guid.NewGuid(), root); + ServiceContext.ContentService.Save(c1); + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, "tttt" + Guid.NewGuid(), c1); + ServiceContext.ContentService.Save(c2); + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 10, out total, filter: "ssss").ToArray(); + Assert.That(entities.Length, Is.EqualTo(10)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 50, out total, filter: "tttt").ToArray(); + Assert.That(entities.Length, Is.EqualTo(50)); + Assert.That(total, Is.EqualTo(50)); + } + [Test] public void EntityService_Can_Get_Paged_Media_Children() { @@ -144,6 +175,40 @@ namespace Umbraco.Tests.Services Assert.That(total, Is.EqualTo(60)); } + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants_With_Search() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + c1.Name = "ssss" + Guid.NewGuid(); + ServiceContext.MediaService.Save(c1); + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + c2.Name = "tttt" + Guid.NewGuid(); + ServiceContext.MediaService.Save(c2); + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 10, out total, filter: "ssss").ToArray(); + Assert.That(entities.Length, Is.EqualTo(10)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 50, out total, filter: "tttt").ToArray(); + Assert.That(entities.Length, Is.EqualTo(50)); + Assert.That(total, Is.EqualTo(50)); + } + [Test] public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 281242621a..5424f29347 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function MiniListViewDirective(contentResource, memberResource, mediaResource) { + function MiniListViewDirective(entityResource) { function link(scope, el, attr, ctrl) { @@ -52,22 +52,17 @@ // start loading animation list view miniListView.loading = true; - - // setup the correct resource depending on section - var resource = ""; - - if (scope.entityType === "Member") { - resource = memberResource.getPagedResults; - } else if (scope.entityType === "Media") { - resource = mediaResource.getChildren; - } else { - resource = contentResource.getChildren; - } - - resource(miniListView.node.id, miniListView.pagination) + + entityResource.getPagedChildren(miniListView.node.id, scope.entityType, miniListView.pagination) .then(function (data) { // update children miniListView.children = data.items; + _.each(miniListView.children, function(c) { + if (c.metaData) { + c.hasChildren = c.metaData.HasChildren; + c.published = c.metaData.IsPublished; + } + }); // update pagination miniListView.pagination.totalItems = data.totalItems; miniListView.pagination.totalPages = data.totalPages; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 3c1c1e2252..f44401f5c5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -477,20 +477,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } - ])), - 'Failed to retrieve children for content item ' + parentId); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + { + id: parentId, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + orderBySystemField: toBool(options.orderBySystemField), + filter: options.filter + })), + 'Failed to retrieve children for content item ' + parentId); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 5e0f5deada..51da9f0958 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -38,6 +38,10 @@ function entityResource($q, $http, umbRequestHelper) { getSafeAlias: function (value, camelCase) { + if (!value) { + return ""; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -68,6 +72,11 @@ function entityResource($q, $http, umbRequestHelper) { * */ getPath: function (id, type) { + + if (id === -1 || id === "-1") { + return "-1"; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -98,7 +107,12 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the url. * */ - getUrl: function(id, type) { + getUrl: function (id, type) { + + if (id === -1 || id === "-1") { + return ""; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -131,7 +145,12 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getById: function (id, type) { + getById: function (id, type) { + + if (id === -1 || id === "-1") { + return null; + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -292,18 +311,19 @@ function entityResource($q, $http, umbRequestHelper) { /** * @ngdoc method - * @name umbraco.resources.entityResource#getAncestors + * @name umbraco.resources.entityResource#getChildren * @methodOf umbraco.resources.entityResource * * @description * Gets children entities for a given item * - * + * @param {Int} parentid id of content item to return children of * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ getChildren: function (id, type) { + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( @@ -312,6 +332,76 @@ function entityResource($q, $http, umbRequestHelper) { [{ id: id }, { type: type }])), 'Failed to retrieve child data for id ' + id); }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged children of a content item with a given id + * + * ##usage + *
+          * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray; 
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getPagedChildren: function (parentId, type, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder" + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPagedChildren", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: options.filter + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 1caff3ab31..3dd57dace4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -72,7 +72,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", $scope.enableSearh = true; //if a alternative startnode is used, we need to check if it is a container - if (dialogOptions.startNodeId && dialogOptions.startNodeId !== -1) { + if (dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") { entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function (node) { if (node.metaData.IsContainer) { openMiniListView(node); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index 97884a6d9e..a2c386e52c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -62,14 +62,14 @@ ng-repeat="child in miniListView.children" ng-click="selectNode(child)" ng-class="{'-selected':child.selected}"> -
+
-   +  
-
{{ child.name }}
+
{{ child.name }}
diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 104c451273..d7630db800 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -29,6 +29,7 @@ using Examine.SearchCriteria; using Umbraco.Web.Dynamics; using umbraco; using System.Text.RegularExpressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Xml; namespace Umbraco.Web.Editors @@ -206,7 +207,7 @@ namespace Umbraco.Web.Editors /// public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) { - //TODO: Rename this!!! It's a bit misleading, it should be GetByXPath + //TODO: Rename this!!! It's misleading, it should be GetByXPath if (type != UmbracoEntityTypes.Document) @@ -264,6 +265,53 @@ namespace Umbraco.Web.Editors return GetResultForChildren(id, type); } + public PagedResult GetPagedChildren( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + long totalRecords; + var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) { return GetResultForAncestors(id, type); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index 87eff89b58..74c6a3ea7e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -84,17 +84,21 @@ namespace Umbraco.Web.Models.ContentEditing } /// - /// The real persisted content object + /// The real persisted content object - used during inbound model binding /// + /// + /// This is not used for outgoing model information. + /// [JsonIgnore] internal TPersisted PersistedContent { get; set; } /// - /// The DTO object used to gather all required content data including data type information etc... for use with validation + /// The DTO object used to gather all required content data including data type information etc... for use with validation - used during inbound model binding /// /// /// We basically use this object to hydrate all required data from the database into one object so we can validate everything we need /// instead of having to look up all the data individually. + /// This is not used for outgoing model information. /// [JsonIgnore] internal ContentItemDto ContentDto { get; set; }