Got media controllers working just like content, have tried to share as much code as possible between them. Can probably do a bit better but there's minimal code replication.

This commit is contained in:
Shannon
2013-07-23 18:55:31 +10:00
parent e7ed08b260
commit 5dc43e99fa
13 changed files with 216 additions and 134 deletions

View File

@@ -1055,6 +1055,7 @@ namespace Umbraco.Core.Services
uow.Commit();
//Special case for the Upload DataType
//TODO: Should we handle this with events?
var uploadDataTypeId = new Guid(Constants.PropertyEditors.UploadField);
if (content.Properties.Any(x => x.PropertyType.DataTypeId == uploadDataTypeId))
{

View File

@@ -6,23 +6,12 @@
<div class="span8">
<div class="btn-toolbar pull-right umb-btn-toolbar">
<div class="btn-group">
<a class="btn" ng-click="preview(content)"
data-shortcut="ctrl+s">Preview page</a>
</div>
<a class="btn btn-success" href="#" ng-click="save(content)" data-shortcut="ctrl+s"
prevent-default>Save</a>
<div class="btn-group">
<a class="btn btn-success" href="#" ng-click="saveAndPublish(content)"
prevent-default>Publish</a>
<a class="btn btn-success dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<li><a href="#" ng-click="save(content)"
prevent-default data-shortcut="ctrl+s">Save draft</a></li>
</ul>
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
function mediaEditController($scope, $routeParams, mediaResource, notificationsService) {
function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) {
if ($routeParams.create) {
@@ -13,6 +13,13 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS
.then(function (data) {
$scope.contentLoaded = true;
$scope.content = data;
//in one particular special case, after we've created a new item we redirect back to the edit
// route but there might be server validation errors in the collection which we need to display
// after the redirect, so we will bind all subscriptions which will show the server validation errors
// if there are any and then clear them so the collection no longer persists them.
serverValidationManager.executeAndClearAllSubscriptions();
});
}
@@ -28,22 +35,27 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS
}
};
//TODO: Clean this up and share this code with the content editor
$scope.saveAndPublish = function (cnt) {
mediaResource.saveMedia(cnt, $routeParams.create, $scope.files)
.then(function (data) {
$scope.content = data;
notificationsService.success("Published", "Media has been saved and published");
});
};
//ensure there is a form object assigned.
var currentForm = angularHelper.getRequiredCurrentForm($scope);
$scope.save = function (cnt) {
$scope.$broadcast("saving", { scope: $scope });
//don't continue if the form is invalid
if (currentForm.$invalid) return;
serverValidationManager.reset();
//TODO: Clean this up and share this code with the content editor
$scope.save = function (cnt) {
mediaResource.saveMedia(cnt, $routeParams.create, $scope.files)
.then(function (data) {
$scope.content = data;
notificationsService.success("Saved", "Media has been saved");
});
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
newContent: data
});
}, function (err) {
contentEditingHelper.handleSaveError(err, $scope);
});
};
}

View File

@@ -777,7 +777,7 @@ To manage your website, simply open the umbraco back office and start adding con
<key alias="editContentSendToPublish">Sent For Approval</key>
<key alias="editContentSendToPublishText">Changes have been sent for approval</key>
<key alias="editMediaSaved">Media saved</key>
<key alias="editMediaSavedText"></key>
<key alias="editMediaSavedText">Media saved without any errors</key>
<key alias="editMemberSaved">Member saved</key>
<key alias="editStylesheetPropertySaved">Stylesheet Property Saved</key>
<key alias="editStylesheetSaved">Stylesheet saved</key>

View File

@@ -763,7 +763,7 @@ To manage your website, simply open the umbraco back office and start adding con
<key alias="editContentSendToPublish">Sent For Approval</key>
<key alias="editContentSendToPublishText">Changes have been sent for approval</key>
<key alias="editMediaSaved">Media saved</key>
<key alias="editMediaSavedText"></key>
<key alias="editMediaSavedText">Media saved without any errors</key>
<key alias="editMemberSaved">Member saved</key>
<key alias="editStylesheetPropertySaved">Stylesheet Property Saved</key>
<key alias="editStylesheetSaved">Stylesheet saved</key>

View File

@@ -22,11 +22,94 @@ using umbraco;
namespace Umbraco.Web.Editors
{
public abstract class ContentControllerBase : UmbracoAuthorizedJsonController
{
/// <summary>
/// Constructor
/// </summary>
protected ContentControllerBase()
: this(UmbracoContext.Current)
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="umbracoContext"></param>
protected ContentControllerBase(UmbracoContext umbracoContext)
: base(umbracoContext)
{
}
protected void HandleContentNotFound(int id)
{
ModelState.AddModelError("id", string.Format("content with id: {0} was not found", id));
var errorResponse = Request.CreateErrorResponse(
HttpStatusCode.NotFound,
ModelState);
throw new HttpResponseException(errorResponse);
}
protected void UpdateName<TPersisted>(ContentItemSave<TPersisted> contentItem)
where TPersisted : IContentBase
{
//Don't update the name if it is empty
if (!contentItem.Name.IsNullOrWhiteSpace())
{
contentItem.PersistedContent.Name = contentItem.Name;
}
}
protected void MapPropertyValues<TPersisted>(ContentItemSave<TPersisted> contentItem)
where TPersisted : IContentBase
{
//Map the property values
foreach (var p in contentItem.ContentDto.Properties)
{
//get the dbo property
var dboProperty = contentItem.PersistedContent.Properties[p.Alias];
//create the property data to send to the property editor
var d = new Dictionary<string, object>();
//add the files if any
var files = contentItem.UploadedFiles.Where(x => x.PropertyId == p.Id).ToArray();
if (files.Any())
{
d.Add("files", files);
}
var data = new ContentPropertyData(p.Value, d);
//get the deserialized value from the property editor
if (p.PropertyEditor == null)
{
LogHelper.Warn<ContentController>("No property editor found for property " + p.Alias);
}
else
{
dboProperty.Value = p.PropertyEditor.ValueEditor.DeserializeValue(data, dboProperty.Value);
}
}
}
protected void HandleInvalidModelState<T, TPersisted>(ContentItemDisplayBase<T, TPersisted> display)
where TPersisted : IContentBase
where T : ContentPropertyBasic
{
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
if (!ModelState.IsValid)
{
display.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Forbidden, display));
}
}
}
/// <summary>
/// The API controller used for editing content
/// </summary>
[PluginController("UmbracoApi")]
public class ContentController : UmbracoAuthorizedJsonController
public class ContentController : ContentControllerBase
{
private readonly ContentModelMapper _contentModelMapper;
@@ -66,11 +149,7 @@ namespace Umbraco.Web.Editors
var foundContent = Services.ContentService.GetById(id);
if (foundContent == null)
{
ModelState.AddModelError("id", string.Format("content with id: {0} was not found", id));
var errorResponse = Request.CreateErrorResponse(
HttpStatusCode.NotFound,
ModelState);
throw new HttpResponseException(errorResponse);
HandleContentNotFound(id);
}
return _contentModelMapper.ToContentItemDisplay(foundContent);
}
@@ -107,43 +186,14 @@ namespace Umbraco.Web.Editors
// * and validated
// * any file attachments have been saved to their temporary location for us to use
// * we have a reference to the DTO object and the persisted object
//Don't update the name if it is empty
if (!contentItem.Name.IsNullOrWhiteSpace())
{
contentItem.PersistedContent.Name = contentItem.Name;
}
UpdateName(contentItem);
//TODO: We need to support 'send to publish'
//TODO: We'll need to save the new template, publishat, etc... values here
//Map the property values
foreach (var p in contentItem.ContentDto.Properties)
{
//get the dbo property
var dboProperty = contentItem.PersistedContent.Properties[p.Alias];
//create the property data to send to the property editor
var d = new Dictionary<string, object>();
//add the files if any
var files = contentItem.UploadedFiles.Where(x => x.PropertyId == p.Id).ToArray();
if (files.Any())
{
d.Add("files", files);
}
var data = new ContentPropertyData(p.Value, d);
//get the deserialized value from the property editor
if (p.PropertyEditor == null)
{
LogHelper.Warn<ContentController>("No property editor found for property " + p.Alias);
}
else
{
dboProperty.Value = p.PropertyEditor.ValueEditor.DeserializeValue(data, dboProperty.Value);
}
}
MapPropertyValues(contentItem);
//We need to manually check the validation results here because:
// * We still need to save the entity even if there are validation value errors
@@ -191,12 +241,9 @@ namespace Umbraco.Web.Editors
//return the updated model
var display = _contentModelMapper.ToContentItemDisplay(contentItem.PersistedContent);
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
if (!ModelState.IsValid)
{
display.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Forbidden, display));
}
HandleInvalidModelState(display);
//put the correct msgs in
switch (contentItem.Action)

View File

@@ -4,6 +4,7 @@ using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
@@ -14,6 +15,7 @@ using Umbraco.Web.WebApi;
using System.Linq;
using Umbraco.Web.WebApi.Binders;
using Umbraco.Web.WebApi.Filters;
using umbraco;
namespace Umbraco.Web.Editors
{
@@ -30,7 +32,7 @@ namespace Umbraco.Web.Editors
//}
[PluginController("UmbracoApi")]
public class MediaController : UmbracoAuthorizedJsonController
public class MediaController : ContentControllerBase
{
private readonly MediaModelMapper _mediaModelMapper;
@@ -67,7 +69,7 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var emptyContent = new Umbraco.Core.Models.Media("Empty", parentId, contentType);
var emptyContent = new Core.Models.Media("Empty", parentId, contentType);
return _mediaModelMapper.ToMediaItemDisplay(emptyContent);
}
@@ -81,11 +83,7 @@ namespace Umbraco.Web.Editors
var foundContent = Services.MediaService.GetById(id);
if (foundContent == null)
{
ModelState.AddModelError("id", string.Format("media with id: {0} was not found", id));
var errorResponse = Request.CreateErrorResponse(
HttpStatusCode.NotFound,
ModelState);
throw new HttpResponseException(errorResponse);
HandleContentNotFound(id);
}
return _mediaModelMapper.ToMediaItemDisplay(foundContent);
}
@@ -123,35 +121,26 @@ namespace Umbraco.Web.Editors
// * any file attachments have been saved to their temporary location for us to use
// * we have a reference to the DTO object and the persisted object
//Now, we just need to save the data
UpdateName(contentItem);
contentItem.PersistedContent.Name = contentItem.Name;
//TODO: We'll need to save the new template, publishat, etc... values here
MapPropertyValues(contentItem);
//Save the property values (for properties that have a valid editor ... not legacy)
foreach (var p in contentItem.ContentDto.Properties.Where(x => x.PropertyEditor != null))
//We need to manually check the validation results here because:
// * We still need to save the entity even if there are validation value errors
// * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null)
// then we cannot continue saving, we can only display errors
// * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display
// a message indicating this
if (!ModelState.IsValid)
{
//get the dbo property
var dboProperty = contentItem.PersistedContent.Properties[p.Alias];
//create the property data to send to the property editor
var d = new Dictionary<string, object>();
//add the files if any
var files = contentItem.UploadedFiles.Where(x => x.PropertyId == p.Id).ToArray();
if (files.Any())
if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem)
&& (contentItem.Action == ContentSaveAction.SaveNew))
{
d.Add("files", files);
}
var data = new ContentPropertyData(p.Value, d);
//get the deserialized value from the property editor
if (p.PropertyEditor == null)
{
LogHelper.Warn<MediaController>("No property editor found for property " + p.Alias);
}
else
{
dboProperty.Value = p.PropertyEditor.ValueEditor.DeserializeValue(data, dboProperty.Value);
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
// add the modelstate to the outgoing object and throw a 403
var forDisplay = _mediaModelMapper.ToMediaItemDisplay(contentItem.PersistedContent);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Forbidden, forDisplay));
}
}
@@ -159,7 +148,21 @@ namespace Umbraco.Web.Editors
Services.MediaService.Save(contentItem.PersistedContent);
//return the updated model
return _mediaModelMapper.ToMediaItemDisplay(contentItem.PersistedContent);
var display = _mediaModelMapper.ToMediaItemDisplay(contentItem.PersistedContent);
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
//put the correct msgs in
switch (contentItem.Action)
{
case ContentSaveAction.Save:
case ContentSaveAction.SaveNew:
display.AddSuccessNotification(ui.Text("speechBubbles", "editMediaSaved"), ui.Text("speechBubbles", "editMediaSavedText"));
break;
}
return display;
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
@@ -13,33 +12,11 @@ namespace Umbraco.Web.Models.ContentEditing
/// A model representing a content item to be displayed in the back office
/// </summary>
[DataContract(Name = "content", Namespace = "")]
public class ContentItemDisplay : TabbedContentItem<ContentPropertyDisplay, IContent>, INotificationModel
public class ContentItemDisplay : ContentItemDisplayBase<ContentPropertyDisplay, IContent>
{
public ContentItemDisplay()
{
Notifications = new List<Notification>();
}
[DataMember(Name = "publishDate")]
public DateTime? PublishDate { get; set; }
/// <summary>
/// This is used for validation of a content item.
/// </summary>
/// <remarks>
/// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will
/// still save the item but it cannot be published. So we need a way of returning validation errors as well as the
/// updated model.
/// </remarks>
[DataMember(Name = "modelState")]
public IDictionary<string, object> Errors { get; set; }
/// <summary>
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
/// </summary>
[DataMember(Name = "notifications")]
public List<Notification> Notifications { get; private set; }
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
{
public abstract class ContentItemDisplayBase<T, TPersisted> : TabbedContentItem<T, TPersisted>, INotificationModel, IErrorModel
where T : ContentPropertyBasic
where TPersisted : IContentBase
{
protected ContentItemDisplayBase()
{
Notifications = new List<Notification>();
}
/// <summary>
/// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes.
/// </summary>
[DataMember(Name = "notifications")]
public List<Notification> Notifications { get; private set; }
/// <summary>
/// This is used for validation of a content item.
/// </summary>
/// <remarks>
/// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will
/// still save the item but it cannot be published. So we need a way of returning validation errors as well as the
/// updated model.
/// </remarks>
[DataMember(Name = "modelState")]
public IDictionary<string, object> Errors { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace Umbraco.Web.Models.ContentEditing
{
public interface IErrorModel
{
/// <summary>
/// This is used for validation of a content item.
/// </summary>
/// <remarks>
/// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will
/// still save the item but it cannot be published. So we need a way of returning validation errors as well as the
/// updated model.
/// </remarks>
IDictionary<string, object> Errors { get; set; }
}
}

View File

@@ -8,7 +8,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// A model representing a content item to be displayed in the back office
/// </summary>
[DataContract(Name = "content", Namespace = "")]
public class MediaItemDisplay : TabbedContentItem<ContentPropertyDisplay, IMedia>
public class MediaItemDisplay : ContentItemDisplayBase<ContentPropertyDisplay, IMedia>
{
}

View File

@@ -8,7 +8,8 @@ using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
{
public abstract class TabbedContentItem<T, TPersisted> : ContentItemBasic<T, TPersisted>
where T : ContentPropertyBasic where TPersisted : IContentBase
where T : ContentPropertyBasic
where TPersisted : IContentBase
{
protected TabbedContentItem()
{

View File

@@ -305,8 +305,10 @@
<Compile Include="Editors\UmbracoAuthorizedJsonController.cs" />
<Compile Include="Editors\ValidationHelper.cs" />
<Compile Include="HttpCookieExtensions.cs" />
<Compile Include="Models\ContentEditing\ContentItemDisplayBase.cs" />
<Compile Include="Models\ContentEditing\ContentSaveAction.cs" />
<Compile Include="Models\ContentEditing\ContentTypeBasic.cs" />
<Compile Include="Models\ContentEditing\IErrorModel.cs" />
<Compile Include="Models\ContentEditing\IHaveUploadedFiles.cs" />
<Compile Include="Models\ContentEditing\INotificationModel.cs" />
<Compile Include="Models\ContentEditing\MediaItemDisplay.cs" />