Boom! container nodes work for both media and content, fixes:

U4-3540 When creating a Container type node - the list view shows all children of the item it's being created underneath
U4-3541 media tree is not catering for container types
This commit is contained in:
Shannon
2013-11-15 16:56:51 +11:00
parent 0878de4a8b
commit 4545af0a21
8 changed files with 364 additions and 308 deletions

View File

@@ -1,221 +1,244 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.ListViewController",
function ($rootScope, $scope, $routeParams, contentResource, contentTypeResource, notificationsService, iconHelper, dialogService) {
function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService) {
$scope.actionInProgress = false;
$scope.listViewResultSet = {
totalPages: 0,
items: []
};
//this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
// that isn't created yet, if we continue this will use the parent id in the route params which isn't what
// we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
// the list view tab entirely when it's new.
if ($routeParams.create) {
$scope.isNew = true;
return;
}
$scope.options = {
pageSize: 10,
pageNumber: 1,
filter: '',
orderBy: 'Id',
orderDirection: "desc"
};
//Now we need to check if this is for media or content because that will depend on the resources we use
var contentResource, contentTypeResource;
if ($scope.model.config.entityType && $scope.model.config.entityType === "media") {
contentResource = $injector.get('mediaResource');
contentTypeResource = $injector.get('mediaTypeResource');
$scope.entityType = "media";
}
else {
contentResource = $injector.get('contentResource');
contentTypeResource = $injector.get('contentTypeResource');
$scope.entityType = "content";
}
$scope.isNew = false;
$scope.actionInProgress = false;
$scope.listViewResultSet = {
totalPages: 0,
items: []
};
$scope.options = {
pageSize: 10,
pageNumber: 1,
filter: '',
orderBy: 'Id',
orderDirection: "desc"
};
$scope.next = function () {
if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber++;
$scope.reloadView($scope.contentId);
}
};
$scope.next = function () {
if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber++;
$scope.reloadView($scope.contentId);
}
};
$scope.goToPage = function (pageNumber) {
$scope.options.pageNumber = pageNumber + 1;
$scope.reloadView($scope.contentId);
};
$scope.goToPage = function (pageNumber) {
$scope.options.pageNumber = pageNumber + 1;
$scope.reloadView($scope.contentId);
};
$scope.sort = function (field) {
$scope.sort = function (field) {
$scope.options.orderBy = field;
$scope.options.orderBy = field;
if ($scope.options.orderDirection === "desc") {
$scope.options.orderDirection = "asc";
} else {
$scope.options.orderDirection = "desc";
}
if ($scope.options.orderDirection === "desc") {
$scope.options.orderDirection = "asc";
} else {
$scope.options.orderDirection = "desc";
}
$scope.reloadView($scope.contentId);
};
$scope.reloadView($scope.contentId);
};
$scope.prev = function () {
if ($scope.options.pageNumber > 1) {
$scope.options.pageNumber--;
$scope.reloadView($scope.contentId);
}
};
$scope.prev = function () {
if ($scope.options.pageNumber > 1) {
$scope.options.pageNumber--;
$scope.reloadView($scope.contentId);
}
};
/*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 */
/*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 (id) {
contentResource.getChildren(id, $scope.options).then(function (data) {
$scope.reloadView = function (id) {
contentResource.getChildren(id, $scope.options).then(function (data) {
$scope.listViewResultSet = data;
$scope.pagination = [];
$scope.listViewResultSet = data;
$scope.pagination = [];
for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) {
$scope.pagination[i] = { index: i, name: i + 1 };
}
if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber = $scope.listViewResultSet.totalPages;
}
});
};
$scope.selectAll = function ($event) {
var checkbox = $event.target;
if (!angular.isArray($scope.listViewResultSet.items)) {
return;
}
for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
var entity = $scope.listViewResultSet.items[i];
entity.selected = checkbox.checked;
}
};
$scope.isSelectedAll = function () {
if (!angular.isArray($scope.listViewResultSet.items)) {
return false;
}
return _.every($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.isAnythingSelected = function () {
if (!angular.isArray($scope.listViewResultSet.items)) {
return false;
}
return _.some($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.getIcon = function (entry) {
return iconHelper.convertFromLegacyIcon(entry.icon);
};
$scope.delete = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
if (confirm("Sure you want to delete?") == true) {
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with delete";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Deleted doc " + current + " out of " + total + " documents";
contentResource.deleteById(selected[i].id).then(function (data) {
if (current === total) {
notificationsService.success("Bulk action", "Deleted " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
}
};
$scope.publish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Publishing " + current + " out of " + total + " documents";
contentResource.publishById(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
}, function (err) {
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
//if there are validation errors for publishing then we need to show them
if (err.status === 400 && err.data && err.data.Message) {
notificationsService.error("Publish error", err.data.Message);
}
else {
dialogService.ysodDialog(err);
}
});
}
};
$scope.unpublish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Unpublishing " + current + " out of " + total + " documents";
contentResource.unPublish(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
};
if ($routeParams.id) {
$scope.pagination = new Array(10);
$scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id);
$scope.reloadView($routeParams.id);
$scope.contentId = $routeParams.id;
for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) {
$scope.pagination[i] = { index: i, name: i + 1 };
}
if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber = $scope.listViewResultSet.totalPages;
}
});
};
$scope.selectAll = function ($event) {
var checkbox = $event.target;
if (!angular.isArray($scope.listViewResultSet.items)) {
return;
}
for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
var entity = $scope.listViewResultSet.items[i];
entity.selected = checkbox.checked;
}
};
$scope.isSelectedAll = function () {
if (!angular.isArray($scope.listViewResultSet.items)) {
return false;
}
return _.every($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.isAnythingSelected = function () {
if (!angular.isArray($scope.listViewResultSet.items)) {
return false;
}
return _.some($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.getIcon = function (entry) {
return iconHelper.convertFromLegacyIcon(entry.icon);
};
$scope.delete = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
if (confirm("Sure you want to delete?") == true) {
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with delete";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Deleted doc " + current + " out of " + total + " documents";
contentResource.deleteById(selected[i].id).then(function (data) {
if (current === total) {
notificationsService.success("Bulk action", "Deleted " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
}
};
$scope.publish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Publishing " + current + " out of " + total + " documents";
contentResource.publishById(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
}, function (err) {
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
//if there are validation errors for publishing then we need to show them
if (err.status === 400 && err.data && err.data.Message) {
notificationsService.error("Publish error", err.data.Message);
}
else {
dialogService.ysodDialog(err);
}
});
}
};
$scope.unpublish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Unpublishing " + current + " out of " + total + " documents";
contentResource.unPublish(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
};
if ($routeParams.id) {
$scope.pagination = new Array(10);
$scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id);
$scope.reloadView($routeParams.id);
$scope.contentId = $routeParams.id;
}
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController);

View File

@@ -1,103 +1,114 @@
<div class="umb-editor umb-listview" ng-controller="Umbraco.PropertyEditors.ListViewController">
<div class="row-fluid">
<div class="umb-sub-header">
<div class="btn-group" ng-show="listViewAllowedTypes">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<localize key="actions_create">Create</localize>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li ng-repeat="contentType in listViewAllowedTypes">
<a
href="#/content/content/edit/{{contentId}}?doctype={{contentType.alias}}&create=true">
<i class="icon-{{contentType.cssClass}}"></i>
{{contentType.name}}
</a>
</li>
</ul>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-success" ng-disabled="actionInProgress" ng-click="publish()" prevent-default>
<localize key="actions_publish">Publish</localize></a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-warning" ng-disabled="actionInProgress" ng-click="unpublish()" prevent-default>
<localize key="actions_unpublish">Unpublish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-danger" ng-disabled="actionInProgress" ng-click="delete()" prevent-default>
<localize key="actions_delete">Delete</localize>
</a>
</div>
<span ng-bind="bulkStatus" ng-show="isAnythingSelected()"></span>
</div>
<div class="umb-editor umb-listview" ng-controller="Umbraco.PropertyEditors.ListViewController" ng-switch="isNew">
<div class="row-fluid" ng-switch-when="true">
<table class="table table-striped">
<thead>
<tr>
<td with="20"><input type="checkbox" ng-click="selectAll($event)" ng-checked="isSelectedAll()"></td>
<td><a href="#" ng-click="sort('Name')" prevent-default>
<localize key="general_name">Name</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('UpdateDate')" prevent-default>
<localize key="defaultdialogs_lastEdited">Last edited</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('Owner')" prevent-default>
<localize key="content_updatedBy">Updated by</localize>
<i class="icon-sort"></i></a></td>
<td with="20"><form class="pull-right" novalidate>
<i class="icon-search"></i>
<input type="text" ng-model="options.filter" on-keyup="reloadView(contentId)">
</form></td>
</tr>
</thead>
</div>
<tbody>
<tr ng-repeat="result in listViewResultSet.items"
ng-class="{selected:result.selected}">
<div class="row-fluid" ng-switch-when="false">
<div class="umb-sub-header">
<td>
<i class="icon {{result.icon}}" ng-class="getIcon(result)"></i>
<div class="btn-group" ng-show="listViewAllowedTypes">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<localize key="actions_create">Create</localize>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li ng-repeat="contentType in listViewAllowedTypes">
<a
href="#/{{entityType}}/{{entityType}}/edit/{{contentId}}?doctype={{contentType.alias}}&create=true">
<i class="icon-{{contentType.cssClass}}"></i>
{{contentType.name}}
</a>
</li>
</ul>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content'">
<a class="btn btn-success" ng-disabled="actionInProgress" ng-click="publish()" prevent-default>
<localize key="actions_publish">Publish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content'">
<a class="btn btn-warning" ng-disabled="actionInProgress" ng-click="unpublish()" prevent-default>
<localize key="actions_unpublish">Unpublish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-danger" ng-disabled="actionInProgress" ng-click="delete()" prevent-default>
<localize key="actions_delete">Delete</localize>
</a>
</div>
<span ng-bind="bulkStatus" ng-show="isAnythingSelected()"></span>
</div>
<input type="checkbox" ng-model="result.selected"></td>
<td>
<a ng-class="{inactive:!result.published}" href="#/content/content/edit/{{result.id}}">{{result.name}}</a></td>
<td>{{result.updateDate|date:'medium'}}
<!--<<span class="label label-success">Publish</span>-->
</td>
<td>{{result.owner.name}} <!--<span class="label">Admin</span>--></td>
<td></td>
</tr>
</tbody>
<table class="table table-striped">
<thead>
<tr>
<td>
<input type="checkbox" ng-click="selectAll($event)" ng-checked="isSelectedAll()"></td>
<td><a href="#" ng-click="sort('Name')" prevent-default>
<localize key="general_name">Name</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('UpdateDate')" prevent-default>
<localize key="defaultdialogs_lastEdited">Last edited</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('Owner')" prevent-default>
<localize key="content_updatedBy">Updated by</localize>
<i class="icon-sort"></i></a></td>
<td>
<form class="pull-right" novalidate>
<i class="icon-search"></i>
<input type="text" ng-model="options.filter" on-keyup="reloadView(contentId)">
</form>
</td>
</tr>
</thead>
<tfoot ng-show="pagination.length > 1">
<tr>
<th colspan="5">
<div class="pull-left">
</div>
<div class="pagination pagination-right">
<ul>
<li><a href="#" ng-click="prev()" prevent-default><localize key="general_previous">Previous</localize></a></li>
<tbody>
<tr ng-repeat="result in listViewResultSet.items"
ng-class="{selected:result.selected}">
<li ng-repeat="pgn in pagination track by $index"
ng-class="{active:$index==options.offset}">
<a href="#" ng-click="goToPage($index)" prevent-default>{{$index + 1}}</a>
</li>
<td>
<i class="icon {{result.icon}}" ng-class="getIcon(result)"></i>
<li><a href="#" ng-click="next()" prevent-default>
<localize key="general_next">Next</localize>
</a></li>
</ul>
</div>
<input type="checkbox" ng-model="result.selected"></td>
<td>
<a ng-class="{inactive: entityType === 'content' && !result.published}" href="#/{{entityType}}/{{entityType}}/edit/{{result.id}}">{{result.name}}</a></td>
<td>{{result.updateDate|date:'medium'}}
<!--<<span class="label label-success">Publish</span>-->
</td>
<td>{{result.owner.name}}
<!--<span class="label">Admin</span>-->
</td>
<td></td>
</tr>
</tbody>
</th>
</tr>
</tfoot>
</table>
</div>
<tfoot ng-show="pagination.length > 1">
<tr>
<th colspan="5">
<div class="pull-left">
</div>
<div class="pagination pagination-right">
<ul>
<li><a href="#" ng-click="prev()" prevent-default>
<localize key="general_previous">Previous</localize>
</a></li>
<li ng-repeat="pgn in pagination track by $index"
ng-class="{active:$index==options.offset}">
<a href="#" ng-click="goToPage($index)" prevent-default>{{$index + 1}}</a>
</li>
<li><a href="#" ng-click="next()" prevent-default>
<localize key="general_next">Next</localize>
</a></li>
</ul>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
</div>

View File

@@ -95,7 +95,10 @@ namespace Umbraco.Web.Editors
/// </summary>
/// <param name="contentTypeAlias"></param>
/// <param name="parentId"></param>
/// <returns></returns>
/// <returns>
/// If this is a container type, we'll remove the umbContainerView tab for a new item since
/// it cannot actually list children if it doesn't exist yet.
/// </returns>
public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId)
{
var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias);
@@ -105,7 +108,12 @@ namespace Umbraco.Web.Editors
}
var emptyContent = new Content("", parentId, contentType);
return Mapper.Map<IContent, ContentItemDisplay>(emptyContent);
var mapped = Mapper.Map<IContent, ContentItemDisplay>(emptyContent);
//remove this tab if it exists: umbContainerView
var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == "umbContainerView");
mapped.Tabs = mapped.Tabs.Except(new[] {containerTab});
return mapped;
}
/// <summary>

View File

@@ -152,7 +152,12 @@ namespace Umbraco.Web.Editors
}
var emptyContent = new Core.Models.Media("", parentId, contentType);
return Mapper.Map<IMedia, MediaItemDisplay>(emptyContent);
var mapped = Mapper.Map<IMedia, MediaItemDisplay>(emptyContent);
//remove this tab if it exists: umbContainerView
var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == "umbContainerView");
mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
return mapped;
}
/// <summary>

View File

@@ -100,7 +100,7 @@ namespace Umbraco.Web.Models.Mapping
if (content.ContentType.IsContainer)
{
TabsAndPropertiesResolver.AddContainerView(display);
TabsAndPropertiesResolver.AddContainerView(display, "content");
}
TabsAndPropertiesResolver.MapGenericProperties(

View File

@@ -61,12 +61,11 @@ namespace Umbraco.Web.Models.Mapping
private static void MapGenericCustomProperties(IMedia media, MediaItemDisplay display)
{
/*
* Should this be added? if so we need some changes in the UI tho.
if (media.ContentType.IsContainer)
{
TabsAndPropertiesResolver.AddContainerView(display);
}*/
TabsAndPropertiesResolver.AddContainerView(display, "media");
}
TabsAndPropertiesResolver.MapGenericProperties(media, display);
}

View File

@@ -108,11 +108,17 @@ namespace Umbraco.Web.Models.Mapping
}
internal static void AddContainerView<TPersisted>(TabbedContentItem<ContentPropertyDisplay, TPersisted> display)
/// <summary>
/// Adds the container (listview) tab to the document
/// </summary>
/// <typeparam name="TPersisted"></typeparam>
/// <param name="display"></param>
/// <param name="entityType">This must be either 'content' or 'media'</param>
internal static void AddContainerView<TPersisted>(TabbedContentItem<ContentPropertyDisplay, TPersisted> display, string entityType)
where TPersisted : IContentBase
{
var listViewTab = new Tab<ContentPropertyDisplay>();
listViewTab.Alias = "containerView";
listViewTab.Alias = "umbContainerView";
listViewTab.Label = ui.Text("content", "childItems");
listViewTab.Id = 25;
listViewTab.IsActive = true;
@@ -124,7 +130,11 @@ namespace Umbraco.Web.Models.Mapping
Label = "",
Value = null,
View = "listview",
HideLabel = true
HideLabel = true,
Config = new Dictionary<string, object>
{
{"entityType", entityType}
}
});
listViewTab.Properties = listViewProperties;

View File

@@ -84,7 +84,7 @@ namespace Umbraco.Web.Trees
}
//before we get the children we need to see if this is a container node
var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectTypes.Document);
var current = Services.EntityService.Get(int.Parse(id), UmbracoObjectType);
if (current != null && current.AdditionalData.ContainsKey("IsContainer") && current.AdditionalData["IsContainer"] is bool && (bool)current.AdditionalData["IsContainer"])
{
//no children!