diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs index 4e1a3de28f..c606c24a59 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Linq.Expressions; using System.Reflection; using Umbraco.Core.Persistence.Querying; @@ -125,5 +126,10 @@ namespace Umbraco.Core.Persistence SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(rightColumnName)); return sql.On(onClause); } + + public static Sql OrderByDescending(this Sql sql, params object[] columns) + { + return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString() + " DESC").ToArray()))); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 42bec777aa..31382452b2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Xml.Linq; using Umbraco.Core.Configuration; +using Umbraco.Core.Dynamics; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -642,7 +644,117 @@ namespace Umbraco.Core.Persistence.Repositories _contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity(previewExists, content, xml)); } - + + /// + /// Gets paged content results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedResultsByQuery(IQuery query, int pageNumber, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, string filter = "") + { + // Get base query + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .Where(x => x.Newest); + + // Apply filter + if (!string.IsNullOrEmpty(filter)) + { + sql = sql.Where("cmsDocument.text LIKE @0", "%" + filter + "%"); + } + + // Apply order according to parameters + if (!string.IsNullOrEmpty(orderBy)) + { + var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; + if (orderDirection == Direction.Ascending) + { + sql = sql.OrderBy(orderByParams); + } + else + { + sql = sql.OrderByDescending(orderByParams); + } + } + + // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, + // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. + // So we'll modify the SQL. + var modifiedSQL = sql.SQL.Replace("SELECT *", "SELECT cmsDocument.nodeId"); + + // Get page of results and total count + IEnumerable result; + var pagedResult = Database.Page(pageNumber, pageSize, modifiedSQL, sql.Arguments); + totalRecords = Convert.ToInt32(pagedResult.TotalItems); + if (totalRecords > 0) + { + // Parse out node Ids and load content (we need the cast here in order to be able to call the IQueryable extension + // methods OrderBy or OrderByDescending) + var content = GetAll(pagedResult.Items + .DistinctBy(x => x.NodeId) + .Select(x => x.NodeId).ToArray()) + .Cast() + .AsQueryable(); + + // Now we need to ensure this result is also ordered by the same order by clause + var orderByProperty = GetIContentPropertyNameForOrderBy(orderBy); + if (orderDirection == Direction.Ascending) + { + result = content.OrderBy(orderByProperty); + } + else + { + result = content.OrderByDescending(orderByProperty); + } + } + else + { + result = Enumerable.Empty(); + } + + return result; + } + + private string GetDatabaseFieldNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the database field names. + switch (orderBy) + { + case "Name": + return "cmsDocument.text"; + case "Owner": + return "umbracoNode.nodeUser"; + case "Updator": + return "cmsDocument.documentUser"; + default: + return orderBy; + } + } + + private string GetIContentPropertyNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the IContent property names. + switch (orderBy) + { + case "Owner": + return "CreatorId"; + case "Updator": + return "WriterId"; + default: + return orderBy; + } + } + #endregion /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 2fd0a5685e..ba99e50641 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Xml.Linq; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories @@ -66,5 +68,18 @@ namespace Umbraco.Core.Persistence.Repositories /// void AddOrUpdatePreviewXml(IContent content, Func xml); + /// + /// Gets paged content results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedResultsByQuery(IQuery query, int pageNumber, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, string filter = ""); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 311e68a482..9ac8ba4162 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Xml.Linq; using Umbraco.Core.Auditing; @@ -13,6 +14,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.SqlSyntax; @@ -473,6 +475,29 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, int pageNumber, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, string filter = "") + { + using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var contents = repository.GetPagedResultsByQuery(query, pageNumber, pageSize, out totalChildren, orderBy, orderDirection, filter); + + return contents; + } + } + /// /// Gets a collection of objects by its name or partial name /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 76964e8e9a..a2e44578a8 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Publishing; namespace Umbraco.Core.Services @@ -108,6 +109,20 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetChildren(int id); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, int pageNumber, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, string filter = ""); + /// /// Gets a collection of an objects versions by its Id /// diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index e75eabff4f..26510f3b5d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -15,6 +15,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.editorControls.tinyMCE3; using umbraco.interfaces; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories { @@ -331,6 +332,132 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 2, 1, out totalRecords, "Name", Direction.Ascending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 2, out totalRecords, "Name", Direction.Ascending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(2)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Descending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, "Page 2"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(1)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, "Page"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + [Test] public void Can_Perform_GetAll_By_Param_Ids_On_ContentRepository() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/utill/nodirtycheck.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/utill/nodirtycheck.directive.js new file mode 100644 index 0000000000..74c007dfbc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/utill/nodirtycheck.directive.js @@ -0,0 +1,18 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:noDirtyCheck +* @restrict A +* @description Can be attached to form inputs to prevent them from setting the form as dirty (http://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs) +**/ +function noDirtyCheck() { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elm, attrs, ctrl) { + elm.focus(function () { + ctrl.$pristine = false; + }); + } + }; +} +angular.module('umbraco.directives').directive("noDirtyCheck", noDirtyCheck); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 57b7eff7c9..d2edfa2f10 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -41,11 +41,7 @@ -

- There are no items show in the list. -

- - +
- + + + + + + + + + - + - diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d2a023ee98..b9cb9d07a2 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -157,47 +157,17 @@ namespace Umbraco.Web.Editors Direction orderDirection = Direction.Ascending, string filter = "") { - //TODO: This will be horribly inefficient for paging! This is because our datasource/repository - // doesn't support paging at the SQL level... and it'll be pretty interesting to try to make that work. - - //PP: could we in 7.0.1+ migrate this to the internal examine index instead of using the content service? - - var children = Services.ContentService.GetChildren(id).ToArray(); - var totalChildren = children.Length; + int totalChildren; + var children = Services.ContentService.GetPagedChildren(id, pageNumber, pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); if (totalChildren == 0) + { return new PagedResult>(0, 0, 0); - - var result = children - .Select(Mapper.Map>) - .AsQueryable(); - - //TODO: This is a rudimentry filter - should use the logic found in the EntityService filter (dynamic linq) instead - if (!string.IsNullOrEmpty(filter)) - { - filter = filter.ToLower(); - result = result.Where(x => x.Name.InvariantContains(filter)); } - var orderedResult = orderDirection == Direction.Ascending - ? result.OrderBy(orderBy) - : result.OrderByDescending(orderBy); - - var pagedResult = new PagedResult>( - totalChildren, - pageNumber, - pageSize); - - if (pageNumber > 0 && pageSize > 0) - { - pagedResult.Items = orderedResult - .Skip(pagedResult.SkipSize) - .Take(pageSize); - } - else - { - pagedResult.Items = orderedResult; - } + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); return pagedResult; }
@@ -62,26 +58,38 @@
- +
+

There are no items show in the list.

+
- - {{result.name}}{{result.updateDate|date:'medium'}} + {{result.name}} + + {{result.updateDate|date:'medium'}} {{result.owner.name}} + + {{result.owner.name}}