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 db99f5c6e0..4a86da02bc 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 @@ -34,37 +34,40 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific pageSize: 10, pageNumber: 1, filter: '', - orderBy: 'UpdateDate', + orderBy: 'updateDate', orderDirection: "desc", allowBulkPublish: true, allowBulkUnpublish: true, allowBulkDelete: true, + additionalColumnAliases: ['UpdateDate', 'Owner'], + additionalColumnHeaders: ['Last edited', 'Updated by'], + additionalColumnLocalizationKeys: ['defaultdialogs_lastEdited', 'content_updatedBy'] }; // Retrieve the container configuration for the content type and set options before presenting initial view contentTypeResource.getContainerConfig($routeParams.id) .then(function (config) { - if (typeof(config.pageSize) !== 'undefined') { + if (typeof config.pageSize !== 'undefined') { $scope.options.pageSize = config.pageSize; } - - if (typeof (config.orderBy) !== 'undefined') { + if (typeof config.additionalColumnAliases !== 'undefined') { + $scope.options.additionalColumnAliases = config.additionalColumnAliases.split(','); + $scope.options.additionalColumnHeaders = config.additionalColumnHeaders.split(','); + $scope.options.additionalColumnLocalizationKeys = config.additionalColumnLocalizationKeys.split(','); + } + if (typeof config.orderBy !== 'undefined') { $scope.options.orderBy = config.orderBy; } - - if (typeof (config.orderDirection) !== 'undefined') { + if (typeof config.orderDirection !== 'undefined') { $scope.options.orderDirection = config.orderDirection; } - - if (typeof (config.allowBulkPublish) !== 'undefined') { + if (typeof config.allowBulkPublish !== 'undefined') { $scope.options.allowBulkPublish = config.allowBulkPublish; } - - if (typeof (config.allowBulkUnpublish) !== 'undefined') { + if (typeof config.allowBulkUnpublish !== 'undefined') { $scope.options.allowBulkUnpublish = config.allowBulkUnpublish; } - - if (typeof (config.allowBulkDelete) !== 'undefined') { + if (typeof config.allowBulkDelete !== 'undefined') { $scope.options.allowBulkDelete = config.allowBulkDelete; } @@ -120,7 +123,6 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific $scope.reloadView = function (id) { contentResource.getChildren(id, $scope.options).then(function (data) { - $scope.listViewResultSet = data; $scope.pagination = []; @@ -135,6 +137,56 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific }); }; + $scope.getColumnName = function (index) { + return $scope.options.additionalColumnHeaders[index]; + }; + + $scope.getColumnLocalizationKey = function (index) { + return $scope.options.additionalColumnLocalizationKeys[index]; + }; + + $scope.getPropertyValue = function (alias, result) { + + // Camel-case the alias + alias = alias.charAt(0).toLowerCase() + alias.slice(1); + + // First try to pull the value directly from the alias (e.g. updatedBy) + var value = result[alias]; + + // If this returns an object, look for the name property of that (e.g. owner.name) + if (value === Object(value)) { + value = value['name']; + } + + // If we've got nothing yet, look at a user defined property + if (typeof value === 'undefined') { + value = $scope.getCustomPropertyValue(alias, result.properties); + } + + // Return what we've got + return value; + + }; + + $scope.getCustomPropertyValue = function (alias, properties) { + var value = ''; + var index = 0; + var foundAlias = false; + for (var i = 0; i < properties.length; i++) { + if (properties[i].alias == alias) { + foundAlias = true; + break; + } + index++; + } + + if (foundAlias) { + value = properties[index].value; + } + + return value; + }; + //assign debounce method to the search to limit the queries $scope.search = _.debounce(function () { $scope.options.pageNumber = 1; 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 8bcd3dd968..d6f419d1e0 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 @@ -49,16 +49,23 @@ - - - Name - - - Last edited - - - Updated by - + + + + + Name + + + + + + + {{getColumnName($index)}} + {{getColumnName($index)}} + + + +
@@ -74,16 +81,16 @@ - - + + - {{result.name}} - {{result.updateDate|date:'medium'}} - + {{result.name}} - {{result.owner.name}} - + + + {{getPropertyValue(alias, result)}} + diff --git a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx b/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx index 9c8adfb236..1c1e4cc625 100644 --- a/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx +++ b/src/Umbraco.Web.UI/umbraco/controls/ContentTypeControlNew.ascx @@ -82,20 +82,19 @@
- + - - - - Node Name - Last Edited Date - Updated By - + + + + + + - + Ascending @@ -103,7 +102,7 @@ - + Yes @@ -111,7 +110,7 @@ - + Yes @@ -119,7 +118,7 @@ - + Yes diff --git a/src/Umbraco.Web/Editors/ContentAndMediaTypeBaseController.cs b/src/Umbraco.Web/Editors/ContentAndMediaTypeBaseController.cs index 1601d6f0f1..ecb9175b97 100644 --- a/src/Umbraco.Web/Editors/ContentAndMediaTypeBaseController.cs +++ b/src/Umbraco.Web/Editors/ContentAndMediaTypeBaseController.cs @@ -1,8 +1,12 @@ -using System.Net; +using System; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; using System.Web.Http; +using Newtonsoft.Json; +using Umbraco.Core; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; -using Newtonsoft.Json; namespace Umbraco.Web.Editors { @@ -35,7 +39,7 @@ namespace Umbraco.Web.Editors /// public ContentTypeContainerConfiguration GetContainerConfig(int contentId) { - var contentItem = Services.ContentService.GetById(contentId); + var contentItem = Services.ContentService.GetById(contentId); if (contentItem == null) { throw new HttpResponseException(HttpStatusCode.NotFound); @@ -43,7 +47,59 @@ namespace Umbraco.Web.Editors if (!string.IsNullOrEmpty(contentItem.ContentType.ContainerConfig)) { - return JsonConvert.DeserializeObject(contentItem.ContentType.ContainerConfig); + var containerConfig = JsonConvert.DeserializeObject(contentItem.ContentType.ContainerConfig); + + // Populate the column headings and localization keys + if (!string.IsNullOrEmpty(containerConfig.AdditionalColumnAliases)) + { + var aliases = containerConfig.AdditionalColumnAliases.Split(','); + var headings = new string[aliases.Length]; + var localizationKeys = new string[aliases.Length]; + + // Find all the properties for doc types that might be in the list + var allowedContentTypeIds = contentItem.ContentType.AllowedContentTypes + .Select(x => x.Id.Value) + .ToArray(); + var allPropertiesOfAllowedContentTypes = Services.ContentTypeService + .GetAllContentTypes(allowedContentTypeIds) + .SelectMany(x => x.PropertyTypes) + .ToList(); + + for (int i = 0; i < aliases.Length; i++) + { + // Try to find heading from custom property (getting the name from the alias) + // - need to look in children of the current content's content type + var property = allPropertiesOfAllowedContentTypes + .FirstOrDefault(x => x.Alias == aliases[i].ToFirstLower()); + if (property != null) + { + headings[i] = property.Name; + } + else if (aliases[i] == "UpdateDate") + { + // Special case to restore hard-coded column titles + headings[i] = "Last edited"; + localizationKeys[i] = "defaultdialogs_lastEdited"; + } + else if (aliases[i] == "Owner") + { + // Special case to restore hard-coded column titles (2) + headings[i] = "Updated by"; + localizationKeys[i] = "content_updatedBy"; + } + else + { + // Otherwise just sentence case the alias + headings[i] = aliases[i].ToFirstUpper().SplitPascalCasing(); + localizationKeys[i] = "content_" + aliases[i].ToFirstLower(); + } + } + + containerConfig.AdditionalColumnHeaders = string.Join(",", headings); + containerConfig.AdditionalColumnLocalizationKeys = string.Join(",", localizationKeys); + } + + return containerConfig; } return null; diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeContainerConfiguration.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeContainerConfiguration.cs index 10249cebfb..7fc3c19dc8 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeContainerConfiguration.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeContainerConfiguration.cs @@ -13,42 +13,56 @@ namespace Umbraco.Web.Models.ContentEditing /// The page size for the list /// [DataMember(Name = "pageSize")] - [Required] public int PageSize { get; set; } + /// + /// The aliases additional columns displayed after the node name in the list + /// + [DataMember(Name = "additionalColumnAliases")] + public string AdditionalColumnAliases { get; set; } + + /// + /// The headers for the additional columns displayed after the node name in the list + /// + /// This isn't persisted, but is calculated from the aliases when passed to the UI + [DataMember(Name = "additionalColumnHeaders")] + public string AdditionalColumnHeaders { get; set; } + + /// + /// The localization keys for the additional columns displayed after the node name in the list + /// + /// This isn't persisted, but is calculated from the aliases when passed to the UI + [DataMember(Name = "additionalColumnLocalizationKeys")] + public string AdditionalColumnLocalizationKeys { get; set; } + /// /// The default order by column for the list /// [DataMember(Name = "orderBy")] - [Required] public string OrderBy { get; set; } /// /// The default order direction for the list /// [DataMember(Name = "orderDirection")] - [Required] public string OrderDirection { get; set; } /// /// Flag for whether bulk publishing is allowed /// [DataMember(Name = "allowBulkPublish")] - [Required] public bool AllowBulkPublish { get; set; } /// /// Flag for whether bulk unpublishing is allowed /// [DataMember(Name = "allowBulkUnpublish")] - [Required] public bool AllowBulkUnpublish { get; set; } /// /// Flag for whether bulk deletion is allowed /// [DataMember(Name = "allowBulkDelete")] - [Required] public bool AllowBulkDelete { get; set; } } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 4bfb3b7b4f..3156cf5cdb 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -367,6 +367,7 @@ namespace umbraco.controls // Set defaults for saving if not all fields are provided var pageSize = 10; + var additionalColumns = "UpdateDate,Owner"; var orderBy = "UpdateDate"; var orderDirection = "desc"; var allowBulkPublish = true; @@ -377,9 +378,15 @@ namespace umbraco.controls configProvided = true; } - if (ddlContainerConfigOrderBy.SelectedIndex > 0) + if (!string.IsNullOrEmpty(txtContainerConfigAdditionalColumns.Text)) { - orderBy = ddlContainerConfigOrderBy.SelectedItem.Value; + additionalColumns = txtContainerConfigAdditionalColumns.Text; + configProvided = true; + } + + if (!string.IsNullOrEmpty(txtContainerConfigOrderBy.Text)) + { + orderBy = txtContainerConfigOrderBy.Text; configProvided = true; } @@ -412,6 +419,7 @@ namespace umbraco.controls var containerConfig = new ContentTypeContainerConfiguration { PageSize = pageSize, + AdditionalColumnAliases = additionalColumns, OrderBy = orderBy, OrderDirection = orderDirection, AllowBulkPublish = allowBulkPublish, @@ -419,7 +427,13 @@ namespace umbraco.controls AllowBulkDelete = allowBulkDelete, }; - return JsonConvert.SerializeObject(containerConfig); + // Serialize the object ignoring nulls so the calculated property AdditionalColumnHeadings is not persisted + return JsonConvert.SerializeObject(containerConfig, + Formatting.None, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }); } return string.Empty; @@ -634,7 +648,6 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); allowAtRoot.Checked = _contentType.AllowAtRoot; cb_isContainer.Checked = _contentType.IsContainerContentType; - ddlContainerConfigOrderBy.SelectedIndex = -1; ddlContainerConfigOrderDirection.SelectedIndex = -1; ddlContainerConfigAllowBulkPublish.SelectedIndex = -1; ddlContainerConfigAllowBulkUnpublish.SelectedIndex = -1; @@ -643,7 +656,8 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); { var containerConfig = GetContentTypeContainerConfigurationFromJsonString(_contentType.ContainerConfig); txtContainerConfigPageSize.Text = containerConfig.PageSize.ToString(); - ddlContainerConfigOrderBy.Items.FindByValue(containerConfig.OrderBy).Selected = true; + txtContainerConfigAdditionalColumns.Text = containerConfig.AdditionalColumnAliases; + txtContainerConfigOrderBy.Text = containerConfig.OrderBy; ddlContainerConfigOrderDirection.Items.FindByValue(containerConfig.OrderDirection).Selected = true; ddlContainerConfigAllowBulkPublish.SelectedIndex = containerConfig.AllowBulkPublish ? 1 : 2; ddlContainerConfigAllowBulkUnpublish.SelectedIndex = containerConfig.AllowBulkUnpublish ? 1 : 2; @@ -1430,8 +1444,11 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); protected global::umbraco.uicontrols.PropertyPanel pp_containerConfigPageSize; protected global::System.Web.UI.WebControls.TextBox txtContainerConfigPageSize; + protected global::umbraco.uicontrols.PropertyPanel pp_containerConfigAdditionalColumns; + protected global::System.Web.UI.WebControls.TextBox txtContainerConfigAdditionalColumns; + protected global::umbraco.uicontrols.PropertyPanel pp_containerConfigOrderBy; - protected global::System.Web.UI.WebControls.DropDownList ddlContainerConfigOrderBy; + protected global::System.Web.UI.WebControls.TextBox txtContainerConfigOrderBy; protected global::umbraco.uicontrols.PropertyPanel pp_containerConfigOrderDirection; protected global::System.Web.UI.WebControls.DropDownList ddlContainerConfigOrderDirection;