Adds RequiredForPersistenceAttribute to easily detect if any entity fails validation in a way that it cannot be persisted to the data store. Changes all tree events to not be jquery events and to not require a 'callback' jquery object, instead we just use angular events broadcast globally, much nicer.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.ModelBinding;
|
||||
|
||||
namespace Umbraco.Core.Models.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom validation attribute which adds additional metadata to the property to indicate that
|
||||
/// the value is required to be persisted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In Umbraco, we persist content even if it is invalid, however there are some properties that are absolutely required
|
||||
/// in order to be persisted such as the Name of the content item. This attribute is re-usable to check for these types of
|
||||
/// properties over any sort of model.
|
||||
/// </remarks>
|
||||
public class RequiredForPersistenceAttribute : RequiredAttribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -251,6 +251,7 @@
|
||||
<Compile Include="Models\UmbracoEntity.cs" />
|
||||
<Compile Include="Models\UmbracoObjectTypes.cs" />
|
||||
<Compile Include="Models\UmbracoObjectTypesExtensions.cs" />
|
||||
<Compile Include="Models\Validation\RequiredForPersistenceAttribute.cs" />
|
||||
<Compile Include="ObjectResolution\ApplicationEventsResolver.cs" />
|
||||
<Compile Include="ObjectResolution\ResolverCollection.cs" />
|
||||
<Compile Include="Persistence\Caching\InMemoryCacheProvider.cs" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @restrict E
|
||||
**/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbTree', function ($compile, $log, $q, treeService, notificationsService, $timeout) {
|
||||
.directive('umbTree', function ($compile, $log, $q, $rootScope, treeService, notificationsService, $timeout) {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -15,8 +15,7 @@ angular.module("umbraco.directives")
|
||||
section: '@',
|
||||
showoptions: '@',
|
||||
showheader: '@',
|
||||
cachekey: '@',
|
||||
callback: '='
|
||||
cachekey: '@'
|
||||
},
|
||||
|
||||
compile: function (element, attrs) {
|
||||
@@ -34,7 +33,7 @@ angular.module("umbraco.directives")
|
||||
'</div>';
|
||||
}
|
||||
template += '<ul>' +
|
||||
'<umb-tree-item ng-repeat="child in tree.root.children" node="child" callback="callback" section="{{section}}" ng-animate="animation()"></umb-tree-item>' +
|
||||
'<umb-tree-item ng-repeat="child in tree.root.children" node="child" section="{{section}}" ng-animate="animation()"></umb-tree-item>' +
|
||||
'</ul>' +
|
||||
'</li>' +
|
||||
'</ul>';
|
||||
@@ -56,9 +55,7 @@ angular.module("umbraco.directives")
|
||||
|
||||
/** Helper function to emit tree events */
|
||||
function emitEvent(eventName, args) {
|
||||
if (scope.callback) {
|
||||
$(scope.callback).trigger(eventName, args);
|
||||
}
|
||||
$rootScope.$broadcast(eventName, args);
|
||||
}
|
||||
|
||||
/** Method to load in the tree data */
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</example>
|
||||
*/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbTreeItem', function($compile, $http, $templateCache, $interpolate, $log, $location, treeService, notificationsService) {
|
||||
.directive('umbTreeItem', function ($compile, $http, $templateCache, $interpolate, $log, $location, $rootScope, treeService, notificationsService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
@@ -46,11 +46,9 @@ angular.module("umbraco.directives")
|
||||
var enableDeleteAnimations = true;
|
||||
|
||||
/** Helper function to emit tree events */
|
||||
function emitEvent(eventName, args){
|
||||
|
||||
if(scope.callback){
|
||||
$(scope.callback).trigger(eventName,args);
|
||||
}
|
||||
function emitEvent(eventName, args) {
|
||||
$rootScope.$broadcast(eventName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,10 +20,7 @@ angular.module('umbraco.services')
|
||||
.factory('navigationService', function ($rootScope, $routeParams, $log, $location, $q, dialogService, treeService, notificationsService) {
|
||||
|
||||
//TODO: would be nicer to set all of the options here first instead of implicitly below!
|
||||
var ui = {
|
||||
//tree event handler everyone can subscribe to (TODO: but why not use angular events instead of jquery ?)
|
||||
tree: $({})
|
||||
};
|
||||
var ui = {};
|
||||
|
||||
function setMode(mode) {
|
||||
switch (mode) {
|
||||
|
||||
@@ -3,7 +3,8 @@ angular.module("umbraco").controller("Umbraco.Dialogs.ContentPickerController",
|
||||
function ($scope) {
|
||||
$scope.treeCallback = $({});
|
||||
|
||||
$scope.treeCallback.bind("treeNodeSelect", function(event, args){
|
||||
//$scope.treeCallback.bind("treeNodeSelect", function(event, args){
|
||||
$scope.$on("treeNodeSelect", function (ev, args) {
|
||||
args.event.preventDefault();
|
||||
args.event.stopPropagation();
|
||||
|
||||
|
||||
@@ -33,19 +33,15 @@ function NavigationController($scope,$rootScope, $location, $log, navigationServ
|
||||
});
|
||||
|
||||
//this reacts to the options item in the tree
|
||||
navigationService.ui.tree.bind("treeOptionsClick", function (ev, args) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
$scope.$on("treeOptionsClick", function (ev, args) {
|
||||
$scope.currentNode = args.node;
|
||||
args.scope = $scope;
|
||||
|
||||
navigationService.showMenu(ev, args);
|
||||
});
|
||||
|
||||
//this reacts to tree items themselves being clicked
|
||||
//the tree directive should not contain any handling, simply just bubble events
|
||||
navigationService.ui.tree.bind("treeNodeSelect", function (ev, args) {
|
||||
$scope.$on("treeNodeSelect", function (ev, args) {
|
||||
|
||||
var n = args.node;
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<!-- the tree -->
|
||||
<div id="tree" class="span5 umb-scrollable umb-panel" auto-scale="0" ng-animate="'slide'">
|
||||
<umb-tree callback="nav.ui.tree" section="{{nav.ui.currentSection}}" ></umb-tree>
|
||||
<umb-tree section="{{nav.ui.currentSection}}" ></umb-tree>
|
||||
</div>
|
||||
|
||||
<!-- The context menu -->
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<IntermediateOutputPath>C:\Users\Shannon\AppData\Local\Temp\vs24C6.tmp\Debug\</IntermediateOutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutputPath>bin\</OutputPath>
|
||||
@@ -96,6 +97,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<IntermediateOutputPath>C:\Users\Shannon\AppData\Local\Temp\vs24C6.tmp\Release\</IntermediateOutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UmbracoExamine\UmbracoExamine.csproj">
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Umbraco.Web.Editors
|
||||
// a message indicating this
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
if (ModelState["Name"] != null && ModelState["Name"].Errors.Any()
|
||||
if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem)
|
||||
&& (contentItem.Action == ContentSaveAction.SaveNew || contentItem.Action == ContentSaveAction.PublishNew))
|
||||
{
|
||||
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
|
||||
@@ -157,6 +157,7 @@ namespace Umbraco.Web.Editors
|
||||
var forDisplay = _contentModelMapper.ToContentItemDisplay(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Forbidden, forDisplay));
|
||||
|
||||
}
|
||||
|
||||
//if the model state is not valid we cannot publish so change it to save
|
||||
|
||||
31
src/Umbraco.Web/Editors/ValidationHelper.cs
Normal file
31
src/Umbraco.Web/Editors/ValidationHelper.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
internal class ValidationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// This will check if any properties of the model are attributed with the RequiredForPersistenceAttribute attribute and if they are it will
|
||||
/// check if that property validates, if it doesn't it means that the current model cannot be persisted because it doesn't have the necessary information
|
||||
/// to be saved.
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is normally used for things like content creating when the name is empty since we cannot actually create a content item when the name is empty.
|
||||
/// This is similar but different from the standard Required validator since we still allow content to be saved when validation fails but there are some
|
||||
/// content fields that are absolutely mandatory for creating/saving.
|
||||
/// </remarks>
|
||||
internal static bool ModelHasRequiredForPersistenceErrors(object model)
|
||||
{
|
||||
var requiredForPersistenceProperties = TypeDescriptor.GetProperties(model).Cast<PropertyDescriptor>()
|
||||
.Where(x => x.Attributes.Cast<Attribute>().Any(a => a is RequiredForPersistenceAttribute));
|
||||
|
||||
var validator = new RequiredForPersistenceAttribute();
|
||||
return requiredForPersistenceProperties.Any(p => !validator.IsValid(p.GetValue(model)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
@@ -32,7 +33,7 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
public int Id { get; set; }
|
||||
|
||||
[DataMember(Name = "name", IsRequired = true)]
|
||||
[Required(AllowEmptyStrings = false, ErrorMessage = "Required")]
|
||||
[RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "properties")]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,14 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user