diff --git a/src/Umbraco.Core/Dynamics/QueryableExtensions.cs b/src/Umbraco.Core/Dynamics/QueryableExtensions.cs new file mode 100644 index 0000000000..7123e49f78 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/QueryableExtensions.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Umbraco.Core.Dynamics +{ + /// + /// Extension methods for IQueryable + /// + /// + /// NOTE: Ordering code taken from: http://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet + /// + /// ANOTHER NOTE: We have a bastardized version of Dynamic Linq existing at Umbraco.Web.Dynamics however it's been hacked so not + /// sure we can use it for anything other than DynamicNode. + /// + internal static class QueryableExtensions + { + public static IOrderedQueryable OrderBy(this IQueryable source, string property) + { + return ApplyOrder(source, property, "OrderBy"); + } + public static IOrderedQueryable OrderByDescending(this IQueryable source, string property) + { + return ApplyOrder(source, property, "OrderByDescending"); + } + public static IOrderedQueryable ThenBy(this IOrderedQueryable source, string property) + { + return ApplyOrder(source, property, "ThenBy"); + } + public static IOrderedQueryable ThenByDescending(this IOrderedQueryable source, string property) + { + return ApplyOrder(source, property, "ThenByDescending"); + } + static IOrderedQueryable ApplyOrder(IQueryable source, string property, string methodName) + { + string[] props = property.Split('.'); + Type type = typeof(T); + ParameterExpression arg = Expression.Parameter(type, "x"); + Expression expr = arg; + foreach (string prop in props) + { + // use reflection (not ComponentModel) to mirror LINQ + PropertyInfo pi = type.GetProperty(prop); + expr = Expression.Property(expr, pi); + type = pi.PropertyType; + } + Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type); + LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg); + + object result = typeof(Queryable).GetMethods().Single( + method => method.Name == methodName + && method.IsGenericMethodDefinition + && method.GetGenericArguments().Length == 2 + && method.GetParameters().Length == 2) + .MakeGenericMethod(typeof(T), type) + .Invoke(null, new object[] { source, lambda }); + return (IOrderedQueryable)result; + } + } +} \ 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 e2c981f62d..acbc97d445 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 7d26a52867..8aabfed80f 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -520,12 +520,17 @@ namespace Umbraco.Core.Services /// Id of the /// True if the content has any children otherwise False public bool HasChildren(int id) + { + return CountChildren(id) > 0; + } + + internal int CountChildren(int id) { using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.ParentId == id); - int count = repository.Count(query); - return count > 0; + var count = repository.Count(query); + return count; } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e1d7708bfd..0963259928 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -700,6 +700,7 @@ + diff --git a/src/Umbraco.Tests/Controllers/WebApiEditors/ContentControllerTests.cs b/src/Umbraco.Tests/Controllers/WebApiEditors/ContentControllerTests.cs new file mode 100644 index 0000000000..49e094edad --- /dev/null +++ b/src/Umbraco.Tests/Controllers/WebApiEditors/ContentControllerTests.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Tests.Controllers.WebApiEditors +{ + //we REALLY need a way to nicely mock the service context, etc... so we don't have to do integration tests... coming soon. + + //public class ContentControllerTests + //{ + //} +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a872fdfd75..de74ccf7ad 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -195,6 +195,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js index 5ffb4929fe..a64f9d7ab5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/content.mocks.js @@ -2,6 +2,49 @@ angular.module('umbraco.mocks'). factory('contentMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { 'use strict'; + function returnChildren(status, data, headers) { + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var pageNumber = mocksUtils.getParameterByName(data, "pageNumber"); + + var filter = mocksUtils.getParameterByName(data, "filter"); + var pageSize = mocksUtils.getParameterByName(data, "pageSize"); + var parentId = mocksUtils.getParameterByName(data, "id"); + + var collection = { pageSize: 10, totalItems: 68, totalPages: 7, pageNumber: pageNumber, filter: filter }; + collection.totalItems = 56 - (filter.length); + if (pageSize > 0) { + collection.totalPages = Math.round(collection.totalItems / collection.pageSize); + } + else { + collection.totalPages = 1; + } + collection.items = []; + + if (collection.totalItems < pageSize || pageSize < 1) { + collection.pageSize = collection.totalItems; + } else { + collection.pageSize = pageSize; + } + + var id = 0; + for (var i = 0; i < collection.pageSize; i++) { + id = (parentId + i) * pageNumber; + var cnt = mocksUtils.getMockContent(id); + + //here we fake filtering + if (filter !== '') { + cnt.name = filter + cnt.name; + } + + collection.items.push(cnt); + } + + return [200, collection, null]; + } + function returnDeletedNode(status, data, headers) { if (!mocksUtils.checkAuth()) { return [401, null, null]; @@ -50,7 +93,11 @@ angular.module('umbraco.mocks'). return { - register: function() { + register: function () { + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetChildren')) + .respond(returnChildren); + $httpBackend .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById')) .respond(returnNodebyId); 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 dda52c5a98..c610901025 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 @@ -76,42 +76,42 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { getChildren: function (parentId, options) { - //TODO: Make this real - + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder" + }; if (options === undefined) { - options = { - take: 10, - offset: 0, - filter: '' - }; + 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 { + options.orderDirection = "Descending"; } - var collection = { take: 10, total: 68, pages: 7, currentPage: options.offset, filter: options.filter }; - collection.total = 56 - (options.filter.length); - collection.pages = Math.round(collection.total / collection.take); - collection.resultSet = []; - - if (collection.total < options.take) { - collection.take = collection.total; - } else { - collection.take = options.take; - } - - - var _id = 0; - for (var i = 0; i < collection.take; i++) { - _id = (parentId + i) * options.offset; - var cnt = this.getById(_id); - - //here we fake filtering - if (options.filter !== '') { - cnt.name = options.filter + cnt.name; - } - - collection.resultSet.push(cnt); - } - - return collection; + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { filter: options.filter } + ])), + 'Failed to retreive children for content item ' + parentId); }, /** saves or updates a content object */ diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js new file mode 100644 index 0000000000..ee15ab1bf3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.sort.controller.js @@ -0,0 +1,37 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.ContentDeleteController + * @function + * + * @description + * The controller for deleting content + */ +function ContentSortController($scope, contentResource, treeService, navigationService) { + + contentResource.getChildren($scope.currentNode.id).then(function(data) { + $scope.itemsToSort = data.items; + }); + + $scope.performSort = function() { + + //contentResource.sort($scope.currentNode.id).then(function () { + // $scope.currentNode.loading = false; + + // //get the root node before we remove it + // var rootNode = treeService.getTreeRoot($scope.currentNode); + + // //TODO: Need to sync tree, etc... + // treeService.removeNode($scope.currentNode); + + // //ensure the recycle bin has child nodes now + // var recycleBin = treeService.getDescendantNode(rootNode, -20); + // recycleBin.hasChildren = true; + + // navigationService.hideMenu(); + //}); + + }; + +} + +angular.module("umbraco").controller("Umbraco.Editors.Content.SortController", ContentSortController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/sort.html b/src/Umbraco.Web.UI.Client/src/views/content/sort.html index 57444c3df0..c39dcea93d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/sort.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/sort.html @@ -1,28 +1,28 @@ -
-
-

Sort children of {{nav.ui.currentNode.name}}

+
+ +
+

Sort children of {{nav.ui.currentNode.name}}

- - - - - - - - - - - - - - - - -
NameLast Update Index
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
Some node13:23 3 July 2024 0
+ + + + + + + + + + + + + + +
NameLast UpdateIndex
{{model.name}}{{model.updateDate}}{{model.sortOrder}}
-
-
- - -
-
\ No newline at end of file +
+
+ + +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 7c57d73fae..5d7acb94e5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -2,25 +2,25 @@ angular.module("umbraco") .controller("Umbraco.Editors.ListViewController", function ($rootScope, $scope, contentResource, contentTypeResource) { $scope.options = { - take: 10, - offset: 0, + pageSize: 10, + pageNumber: 1, filter: '', - sortby: 'id', - order: "desc" + orderBy: 'id', + orderDirection: "desc" }; - $scope.pagination = new Array(100); + $scope.pagination = []; $scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($scope.content.id); $scope.next = function(){ - if($scope.options.offset < $scope.listViewResultSet.pages){ - $scope.options.offset++; + if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber++; $scope.reloadView(); } }; - $scope.goToOffset = function(offset){ - $scope.options.offset = offset; + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber + 1; $scope.reloadView(); }; @@ -38,8 +38,8 @@ angular.module("umbraco") }; $scope.prev = function(){ - if($scope.options.offset > 0){ - $scope.options.offset--; + if ($scope.options.pageNumber > 1) { + $scope.options.pageNumber--; $scope.reloadView(); } @@ -48,17 +48,23 @@ angular.module("umbraco") /*Loads the search results, based on parameters set in prev,next,sort and so on*/ /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state with simple values */ - $scope.reloadView = function(){ - $scope.listViewResultSet = contentResource.getChildren($scope.content.id, $scope.options); - + $scope.reloadView = function() { + + contentResource.getChildren($scope.content.id, $scope.options).then(function(data) { + + $scope.listViewResultSet = data; + $scope.pagination = []; - for (var i = $scope.listViewResultSet.pages - 1; i >= 0; i--) { - $scope.pagination[i] = {index: i, name: i+1}; + for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) { + $scope.pagination[i] = { index: i, name: i + 1 }; }; - - if($scope.options.offset > $scope.listViewResultSet.pages){ - $scope.options.offset = $scope.listViewResultSet.pages; - } + + if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber = $scope.listViewResultSet.totalPages; + } + + }); + }; $scope.reloadView(); 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 2559381c9b..491f0a9feb 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 @@ -31,7 +31,7 @@ - + {{result.name}} {{result.updateDate|date:'medium'}} @@ -54,7 +54,7 @@
  • - {{$index + 1}} + {{$index + 1}}
  • Next
  • diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-factory.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-factory.spec.js index 8c65c01191..ff91bd65b4 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-factory.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-factory.spec.js @@ -41,17 +41,26 @@ describe('content factory tests', function () { }); - it('should return a content children collection given an id', function () { - var collection = contentFactory.getChildren(1234, undefined); - $rootScope.$digest(); - $httpBackend.flush(); - expect(collection.resultSet.length).toBe(10); + it('should return a all children collection given an id', function () { - collection = contentFactory.getChildren(1234,{take: 5, offset: 1, filter: ""}); + var collection; + contentFactory.getChildren(1234, undefined).then(function (data) { + collection = data; + }); + $rootScope.$digest(); + $httpBackend.flush(); + expect(collection.items.length).toBe(56); + }); + + it('should return paged children collection given an id', function () { + + var collection; + contentFactory.getChildren(1234, { pageSize: 5, pageNumber: 1, filter: "" }).then(function (data) { + collection = data; + }); $rootScope.$digest(); $httpBackend.flush(); - - expect(collection.resultSet.length).toBe(5); - }); + expect(collection.items.length).toBe(5); + }); }); }); \ No newline at end of file diff --git a/src/Umbraco.Web/Dynamics/ExpressionParser.cs b/src/Umbraco.Web/Dynamics/ExpressionParser.cs index 7d57352b44..1ab7b28716 100644 --- a/src/Umbraco.Web/Dynamics/ExpressionParser.cs +++ b/src/Umbraco.Web/Dynamics/ExpressionParser.cs @@ -10,6 +10,9 @@ using Umbraco.Web.Models; namespace Umbraco.Web.Dynamics { + //SD: I wish all of this wasn't hacked and was just the original dynamic linq from MS... sigh. Just + // means we can't really use it for anything other than dynamic node (i think) + internal class ExpressionParser { struct Token diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 5c3bdaf459..9b126ec3d7 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -3,14 +3,17 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Formatting; using System.Web.Http; using System.Web.Http.ModelBinding; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Publishing; using Umbraco.Core.Services; +using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; @@ -20,6 +23,7 @@ using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; using umbraco; using Umbraco.Core.Models; +using Umbraco.Core.Dynamics; namespace Umbraco.Web.Editors { @@ -85,8 +89,59 @@ namespace Umbraco.Web.Editors var emptyContent = new Content("", parentId, contentType); return Mapper.Map(emptyContent); } - + /// + /// Gets the children for the content id passed in + /// + /// + [OutgoingDateTimeFormat] + public PagedResult> GetChildren( + int id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + //TODO: Not sure how to handle 'filter' just yet! + + //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. + + var foundContent = Services.ContentService.GetById(id); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var totalChildren = ((ContentService) Services.ContentService).CountChildren(id); + var result = foundContent.Children() + .Select(Mapper.Map>) + .AsQueryable(); + + 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; + } + + return pagedResult; + } + /// /// Saves content /// /// diff --git a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs index 71091601bd..c9b1effa7f 100644 --- a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs +++ b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs @@ -22,8 +22,10 @@ namespace Umbraco.Web.Editors protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext) { base.Initialize(controllerContext); - controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter); + controllerContext.EnsureJsonOutputOnly(); } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs index c1adf1126f..38cc08cf85 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemBasic.cs @@ -63,6 +63,9 @@ namespace Umbraco.Web.Models.ContentEditing [Required(AllowEmptyStrings = false)] public string ContentTypeAlias { get; set; } + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } + /// /// The real persisted content object /// diff --git a/src/Umbraco.Web/Models/Mapping/BaseContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/BaseContentModelMapper.cs deleted file mode 100644 index e922887cfa..0000000000 --- a/src/Umbraco.Web/Models/Mapping/BaseContentModelMapper.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - internal class BaseContentModelMapper - { - protected ApplicationContext ApplicationContext { get; private set; } - protected UserModelMapper UserMapper { get; private set; } - - public BaseContentModelMapper(ApplicationContext applicationContext, UserModelMapper userMapper) - { - ApplicationContext = applicationContext; - UserMapper = userMapper; - } - - protected ContentItemBasic ToContentItemSimpleBase(IContentBase content) - where TPersisted : IContentBase - { - return CreateContent, ContentPropertyBasic, TPersisted>(content, null, null); - } - - protected IList> GetTabs(IContentBase content) - { - var tabs = content.PropertyGroups.Select(propertyGroup => - { - //get the properties for the current tab - var propertiesForTab = content.GetPropertiesForGroup(propertyGroup).ToArray(); - - //convert the properties to ContentPropertyDisplay objects - var displayProperties = propertiesForTab - .Select(ToContentPropertyDisplay); - - //return the tab with the tab properties - return new Tab - { - Id = propertyGroup.Id, - Alias = propertyGroup.Name, - Label = propertyGroup.Name, - Properties = displayProperties, - IsActive = false - }; - }).ToList(); - - //now add the generic properties tab for any properties that don't belong to a tab - var orphanProperties = content.GetNonGroupedProperties(); - - //now add the generic properties tab - tabs.Add(new Tab - { - Id = 0, - Label = "Generic properties", - Alias = "Generic properties", - Properties = orphanProperties.Select(ToContentPropertyDisplay).ToArray() - }); - - //set the first tab to active - tabs.First().IsActive = true; - - return tabs; - } - - protected TContent CreateContent(IContentBase content, - Action contentCreatedCallback = null, - Action propertyCreatedCallback = null, - bool createProperties = true) - where TContent : ContentItemBasic, new() - where TContentProperty : ContentPropertyBasic, new() - where TPersisted : IContentBase - { - var result = new TContent - { - Id = content.Id, - Owner = UserMapper.ToUserBasic(content.GetCreatorProfile()), - - ParentId = content.ParentId, - UpdateDate = content.UpdateDate, - CreateDate = content.CreateDate, - Name = content.Name - }; - if (createProperties) - result.Properties = content.Properties.Select(p => CreateProperty(p, propertyCreatedCallback)).ToArray(); - if (contentCreatedCallback != null) - contentCreatedCallback(result, content); - return result; - } - - protected ContentPropertyDisplay ToContentPropertyDisplay(Property property) - { - return CreateProperty(property, (display, originalProp, propEditor) => - { - //set the display properties after mapping - display.Alias = originalProp.Alias; - display.Description = originalProp.PropertyType.Description; - display.Label = property.PropertyType.Name; - display.Config = ApplicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId); - if (propEditor == null) - { - //if there is no property editor it means that it is a legacy data type - // we cannot support editing with that so we'll just render the readonly value view. - display.View = GlobalSettings.Path.EnsureEndsWith('/') + - "views/propertyeditors/umbraco/readonlyvalue/readonlyvalue.html"; - } - else - { - display.View = propEditor.ValueEditor.View; - } - - }); - } - - /// - /// Creates the property with the basic property values mapped - /// - /// - /// - /// - /// - protected static TContentProperty CreateProperty( - Property property, - Action callback = null) - where TContentProperty : ContentPropertyBasic, new() - { - var editor = PropertyEditorResolver.Current.GetById(property.PropertyType.DataTypeId); - if (editor == null) - { - //TODO: Remove this check as we shouldn't support this at all! - var legacyEditor = DataTypesResolver.Current.GetById(property.PropertyType.DataTypeId); - if (legacyEditor == null) - { - throw new NullReferenceException("The property editor with id " + property.PropertyType.DataTypeId + " does not exist"); - } - - var legacyResult = new TContentProperty - { - Id = property.Id, - Value = property.Value == null ? "" : property.Value.ToString(), - Alias = property.Alias - }; - if (callback != null) callback(legacyResult, property, null); - return legacyResult; - } - var result = new TContentProperty - { - Id = property.Id, - Value = editor.ValueEditor.SerializeValue(property.Value), - Alias = property.Alias - }; - if (callback != null) callback(result, property, editor); - return result; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/PagedResult.cs b/src/Umbraco.Web/Models/PagedResult.cs new file mode 100644 index 0000000000..d6545923cc --- /dev/null +++ b/src/Umbraco.Web/Models/PagedResult.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + /// + /// Represents a paged result for a model collection + /// + /// + [DataContract(Name = "pagedCollection", Namespace = "")] + public class PagedResult + { + public PagedResult(long totalItems, long pageNumber, long pageSize) + { + TotalItems = totalItems; + PageNumber = pageNumber; + PageSize = pageSize; + + if (pageSize > 0) + { + TotalPages = (long) Math.Ceiling(totalItems/(Decimal) pageSize); + } + else + { + TotalPages = 1; + } + } + + [DataMember(Name = "pageNumber")] + public long PageNumber { get; private set; } + [DataMember(Name = "pageSize")] + public long PageSize { get; private set; } + [DataMember(Name = "totalPages")] + public long TotalPages { get; private set; } + [DataMember(Name = "totalItems")] + public long TotalItems { get; private set; } + [DataMember(Name = "items")] + public IEnumerable Items { get; set; } + + /// + /// Calculates the skip size based on the paged parameters specified + /// + /// + /// Returns 0 if the page number or page size is zero + /// + internal int SkipSize + { + get + { + if (PageNumber > 0 && PageSize > 0) + { + return Convert.ToInt32((PageNumber - 1)*PageSize); + } + return 0; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2753e58788..8ff5738332 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -298,6 +298,9 @@ + + + @@ -321,7 +324,6 @@ - @@ -496,6 +498,7 @@ + diff --git a/src/Umbraco.Web/WebApi/ControllerContextExtensions.cs b/src/Umbraco.Web/WebApi/ControllerContextExtensions.cs new file mode 100644 index 0000000000..1fb31a1944 --- /dev/null +++ b/src/Umbraco.Web/WebApi/ControllerContextExtensions.cs @@ -0,0 +1,36 @@ +using System.Web.Http.Controllers; + +namespace Umbraco.Web.WebApi +{ + internal static class ControllerContextExtensions + { + /// + /// Sets the JSON datetime format to be a custom one + /// + /// + /// + internal static void SetOutgoingDateTimeFormat(this HttpControllerContext controllerContext, string format) + { + var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter; + jsonFormatter.SerializerSettings.Converters.Add(new CustomDateTimeConvertor(format)); + } + + /// + /// Sets the JSON datetime format to be our regular iso standard + /// + internal static void SetOutgoingDateTimeFormat(this HttpControllerContext controllerContext) + { + var jsonFormatter = controllerContext.Configuration.Formatters.JsonFormatter; + jsonFormatter.SerializerSettings.Converters.Add(new CustomDateTimeConvertor("yyyy-MM-dd HH:mm:ss")); + } + + /// + /// Removes the xml formatter so it only outputs json + /// + /// + internal static void EnsureJsonOutputOnly(this HttpControllerContext controllerContext) + { + controllerContext.Configuration.Formatters.Remove(controllerContext.Configuration.Formatters.XmlFormatter); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs b/src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs new file mode 100644 index 0000000000..1291fd52dc --- /dev/null +++ b/src/Umbraco.Web/WebApi/CustomDateTimeConvertor.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Umbraco.Core; + +namespace Umbraco.Web.WebApi +{ + /// + /// Used to convert the format of a DateTime object when serializing + /// + internal class CustomDateTimeConvertor : IsoDateTimeConverter + { + private readonly string _dateTimeFormat; + + public CustomDateTimeConvertor(string dateTimeFormat) + { + Mandate.ParameterNotNullOrEmpty(dateTimeFormat, "dateTimeFormat"); + _dateTimeFormat = dateTimeFormat; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(((DateTime)value).ToString(_dateTimeFormat)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormat.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormat.cs new file mode 100644 index 0000000000..8f8f26205a --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingDateTimeFormat.cs @@ -0,0 +1,46 @@ +using System.Web.Http.Filters; +using Umbraco.Core; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Sets the json outgoing/serialized datetime format + /// + internal class OutgoingDateTimeFormatAttribute : ActionFilterAttribute + { + private readonly string _format; + + /// + /// Specify a custom format + /// + /// + public OutgoingDateTimeFormatAttribute(string format) + { + Mandate.ParameterNotNullOrEmpty(format, "format"); + _format = format; + } + + /// + /// Will use the standard ISO format + /// + public OutgoingDateTimeFormatAttribute() + { + + } + + public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) + { + base.OnActionExecuting(actionContext); + + if (_format.IsNullOrWhiteSpace()) + { + actionContext.ControllerContext.SetOutgoingDateTimeFormat(); + } + else + { + actionContext.ControllerContext.SetOutgoingDateTimeFormat(_format); + } + + } + } +} \ No newline at end of file diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNodeList.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNodeList.cs index e7137e0977..28381a14ae 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNodeList.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNodeList.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Dynamic; using Umbraco.Core; +using Umbraco.Core.Dynamics; using umbraco.interfaces; using System.Collections; using System.Reflection;