diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 5c09deadb8..0333bbcf2b 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -29,6 +29,7 @@ namespace Umbraco.Core.Models private int _creatorId; private bool _allowedAsRoot; private bool _isContainer; + private string _containerConfig; private bool _trashed; private PropertyGroupCollection _propertyGroups; private PropertyTypeCollection _propertyTypes; @@ -72,6 +73,7 @@ namespace Umbraco.Core.Models private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); private static readonly PropertyInfo AllowedAsRootSelector = ExpressionHelper.GetPropertyInfo(x => x.AllowedAsRoot); private static readonly PropertyInfo IsContainerSelector = ExpressionHelper.GetPropertyInfo(x => x.IsContainer); + private static readonly PropertyInfo ContainerConfigSelector = ExpressionHelper.GetPropertyInfo(x => x.ContainerConfig); private static readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); private static readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedContentTypes); private static readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyGroups); @@ -303,6 +305,23 @@ namespace Umbraco.Core.Models } } + /// + /// Gets or Sets a JSON string defining the configuration for this ContentType if set as a container + /// + [DataMember] + public virtual string ContainerConfig + { + get { return _containerConfig; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _containerConfig = value; + return _containerConfig; + }, _containerConfig, ContainerConfigSelector); + } + } + /// /// Boolean indicating whether this ContentType is Trashed or not. /// If ContentType is Trashed it will be located in the Recyclebin. diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 89fca20b8e..57195762c1 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -43,6 +43,11 @@ namespace Umbraco.Core.Models /// bool IsContainer { get; set; } + /// + /// Gets or Sets a JSON string defining the configuration for this ContentType if set as a container + /// + string ContainerConfig { get; set; } + /// /// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType /// diff --git a/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs index 8d163abf44..9651e94e71 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs @@ -39,6 +39,10 @@ namespace Umbraco.Core.Models.Rdbms [Constraint(Default = "0")] public bool IsContainer { get; set; } + [Column("containerConfig")] + [NullSetting(NullSetting = NullSettings.Null)] + public string ContainerConfig { get; set; } + [Column("allowAtRoot")] [Constraint(Default = "0")] public bool AllowAtRoot { get; set; } diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs index 4833cb33f0..8118ca1bad 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { @@ -62,6 +63,10 @@ namespace Umbraco.Core.Models.Rdbms [Column("isContainer")] public bool IsContainer { get; set; } + [Column("containerConfig")] + [NullSetting(NullSetting = NullSettings.Null)] + public string ContainerConfig { get; set; } + [Column("allowAtRoot")] public bool AllowAtRoot { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index cc9ab387f8..20686269b0 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -38,6 +38,7 @@ namespace Umbraco.Core.Persistence.Factories CreatorId = dto.ContentTypeDto.NodeDto.UserId.Value, AllowedAsRoot = dto.ContentTypeDto.AllowAtRoot, IsContainer = dto.ContentTypeDto.IsContainer, + ContainerConfig = dto.ContentTypeDto.ContainerConfig, Trashed = dto.ContentTypeDto.NodeDto.Trashed, DefaultTemplateId = dto.TemplateNodeId }; @@ -74,6 +75,7 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, AllowAtRoot = entity.AllowedAsRoot, IsContainer = entity.IsContainer, + ContainerConfig = entity.ContainerConfig, NodeDto = BuildNodeDto(entity) }; return contentTypeDto; diff --git a/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs index ee2709a79e..498e35b83d 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaTypeFactory.cs @@ -37,6 +37,7 @@ namespace Umbraco.Core.Persistence.Factories CreatorId = dto.NodeDto.UserId.Value, AllowedAsRoot = dto.AllowAtRoot, IsContainer = dto.IsContainer, + ContainerConfig = dto.ContainerConfig, Trashed = dto.NodeDto.Trashed }; //on initial construction we don't want to have dirty properties tracked @@ -56,6 +57,7 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, AllowAtRoot = entity.AllowedAsRoot, IsContainer = entity.IsContainer, + ContainerConfig = entity.ContainerConfig, NodeDto = BuildNodeDto(entity) }; return contentTypeDto; diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs index 49feb5f8c8..f37db94c87 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeFactory.cs @@ -32,6 +32,7 @@ namespace Umbraco.Core.Persistence.Factories NodeId = entity.Id, AllowAtRoot = entity.AllowedAsRoot, IsContainer = entity.IsContainer, + ContainerConfig = entity.ContainerConfig, NodeDto = BuildNodeDto(entity) }; return contentTypeDto; diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 2a59f564e2..37f987930e 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -23,6 +23,7 @@ namespace Umbraco.Core.Persistence.Factories Icon = dto.Icon, Id = dto.NodeId, IsContainer = dto.IsContainer, + ContainerConfig = dto.ContainerConfig, Key = dto.UniqueId.Value, Level = dto.Level, Name = dto.Text, diff --git a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs index 943b42b2ba..c8b619161d 100644 --- a/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/ContentTypeMapper.cs @@ -47,6 +47,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Description, dto => dto.Description); CacheMap(src => src.Icon, dto => dto.Icon); CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.ContainerConfig, dto => dto.ContainerConfig); CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs index 70e4b5c861..912ae19e2f 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MediaTypeMapper.cs @@ -47,6 +47,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Description, dto => dto.Description); CacheMap(src => src.Icon, dto => dto.Icon); CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.ContainerConfig, dto => dto.ContainerConfig); CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs index 69a5190b46..192c3ee090 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MemberTypeMapper.cs @@ -48,6 +48,7 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.Description, dto => dto.Description); CacheMap(src => src.Icon, dto => dto.Icon); CacheMap(src => src.IsContainer, dto => dto.IsContainer); + CacheMap(src => src.ContainerConfig, dto => dto.ContainerConfig); CacheMap(src => src.Thumbnail, dto => dto.Thumbnail); } } diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index c260b10418..b56d854044 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -305,7 +305,8 @@ namespace Umbraco.Core.Services new XElement("Thumbnail", contentType.Thumbnail), new XElement("Description", contentType.Description), new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()), - new XElement("IsListView", contentType.IsContainer.ToString())); + new XElement("IsListView", contentType.IsContainer.ToString()), + new XElement("ContainerConfig", contentType.ContainerConfig.ToString())); var masterContentType = contentType.CompositionAliases().FirstOrDefault(); if (masterContentType != null) diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 47310ab1c3..44551096c5 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -423,8 +423,17 @@ namespace Umbraco.Core.Services //NOTE IsListView is a new property in the package xml so we need to verify it exists before using it. var isListView = infoElement.Element("IsListView"); if (isListView != null) + { contentType.IsContainer = isListView.Value.InvariantEquals("true"); + //NOTE ContainerConfig is a new property in the package xml so we need to verify it exists before using it. + var containerConfig = infoElement.Element("ContainerConfig"); + if (containerConfig != null) + { + contentType.ContainerConfig = containerConfig.Value; + } + } + UpdateContentTypesAllowedTemplates(contentType, infoElement.Element("AllowedTemplates"), defaultTemplateElement); UpdateContentTypesTabs(contentType, documentType.Element("Tabs")); UpdateContentTypesProperties(contentType, documentType.Element("GenericProperties")); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index 16a948bd4f..afacf4c88d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -68,6 +68,38 @@ function contentTypeResource($q, $http, umbRequestHelper) { "GetAllowedChildren", [{ contentId: contentId }])), 'Failed to retrieve data for content id ' + contentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentTypeResource#getContainerConfig + * @methodOf umbraco.resources.contentTypeResource + * + * @description + * Returns a JSON structure for configuration of the container content type + * + * ##usage + *
+         * contentTypeResource.getContainerConfig(1234)
+         *    .then(function(config) {
+         *      $scope.options = {
+         *         pageSize: config.pageSize,
+         *      };
+         *    });
+         * 
+ * @param {Int} contentId id of the content item to retrive the container config for + * @returns {Promise} resourcePromise object. + * + */ + getContainerConfig: function (contentId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentTypeApiBaseUrl", + "GetContainerConfig", + [{ contentId: contentId }])), + 'Failed to retrieve container config data for content id ' + contentId); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index 94699d4195..9f4ac83c11 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -35,6 +35,38 @@ function mediaTypeResource($q, $http, umbRequestHelper) { "GetAllowedChildren", [{ contentId: mediaId }])), 'Failed to retrieve allowed types for media id ' + mediaId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaTypeResource#getContainerConfig + * @methodOf umbraco.resources.mediaTypeResource + * + * @description + * Returns a JSON structure for configuration of the container content type + * + * ##usage + *
+         * mediaTypeResource.getContainerConfig(1234)
+         *    .then(function(config) {
+         *      $scope.options = {
+         *         pageSize: config.pageSize,
+         *      };
+         *    });
+         * 
+ * @param {Int} contentId id of the content item to retrive the container config for + * @returns {Promise} resourcePromise object. + * + */ + getContainerConfig: function (contentId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaTypeApiBaseUrl", + "GetContainerConfig", + [{ contentId: contentId }])), + 'Failed to retrieve container config data for media id ' + contentId); } }; 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 379504cf71..8789dc1adb 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 @@ -29,14 +29,19 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific items: [] }; - $scope.options = { - pageSize: 10, - pageNumber: 1, - filter: '', - orderBy: 'UpdateDate', - orderDirection: "desc" - }; + // Retrieve the container configuration for the content type and set options before presenting initial view + contentTypeResource.getContainerConfig($routeParams.id) + .then(function (config) { + $scope.options = { + pageSize: config.pageSize, + pageNumber: 1, + filter: '', + orderBy: 'UpdateDate', + orderDirection: "desc" + }; + $scope.initView(); + }); $scope.next = function () { if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) { @@ -54,14 +59,12 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific $scope.options.orderBy = field; - if ($scope.options.orderDirection === "desc") { $scope.options.orderDirection = "asc"; } else { $scope.options.orderDirection = "desc"; } - $scope.reloadView($scope.contentId); }; @@ -72,6 +75,17 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific } }; + $scope.initView = function () { + if ($routeParams.id) { + $scope.pagination = new Array(10); + $scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id); + $scope.reloadView($routeParams.id); + + $scope.contentId = $routeParams.id; + $scope.isTrashed = $routeParams.id === "-20" || $routeParams.id === "-21"; + } + }; + /*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 */ @@ -80,7 +94,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific contentResource.getChildren(id, $scope.options).then(function (data) { $scope.listViewResultSet = data; - $scope.pagination = []; + $scope.pagination = []; for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) { $scope.pagination[i] = { index: i, name: i + 1 }; @@ -236,16 +250,6 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific } }; - if ($routeParams.id) { - $scope.pagination = new Array(10); - $scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id); - $scope.reloadView($routeParams.id); - - $scope.contentId = $routeParams.id; - $scope.isTrashed = $routeParams.id === "-20" || $routeParams.id === "-21"; - - } - } angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController); \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 10c4d31a6a..a0bc01e9a1 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -12,6 +12,7 @@ using Umbraco.Web.WebApi; using System.Linq; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; +using Newtonsoft.Json; namespace Umbraco.Web.Editors { @@ -86,6 +87,21 @@ namespace Umbraco.Web.Editors return basics; } + /// + /// Returns the container configuration JSON structure for the content item id passed in + /// + /// + public ContentTypeContainerConfiguration GetContainerConfig(int contentId) + { + var contentItem = Services.ContentService.GetById(contentId); + if (contentItem == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + return JsonConvert.DeserializeObject(contentItem.ContentType.ContainerConfig); + } + // TODO: This should really be centralized and used anywhere globalization applies. internal string TranslateItem(string text) { diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index bb27ef7b61..74b262b8f4 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -61,11 +61,24 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - return contentItem.ContentType.AllowedContentTypes .Select(x => Services.ContentTypeService.GetMediaType((int) x.Id.Value)) .Select(Mapper.Map); + } + /// + /// Returns the container configuration JSON structure for the content item id passed in + /// + /// + public JsonNetResult GetContainerConfig(int contentId) + { + var contentItem = Services.ContentService.GetById(contentId); + if (contentItem == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + return JsonConvert.DeserializeObject(contentItem.ContentType.ContainerConfig); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeContainerConfiguration.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeContainerConfiguration.cs new file mode 100644 index 0000000000..291b5e1f0c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeContainerConfiguration.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// A model representing the configuration for a content type defined as a list container + /// + [DataContract(Name = "config", Namespace = "")] + public class ContentTypeContainerConfiguration + { + /// + /// The page size for the list + /// + [DataMember(Name = "pageSize", IsRequired = true)] + [Required] + public int PageSize { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index aba7c4d109..ef318cb9af 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -300,6 +300,7 @@ +