From a29706b5ce0186ec213cec75b16d985cedfad540 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Aug 2013 11:33:41 +1000 Subject: [PATCH 1/6] Updated the model mappers to include the pre-value value which is now wired up to the display, now just need to get the data type's persisting. --- src/Umbraco.Core/Models/DataTypeDefinition.cs | 2 + .../datatype/datatype.edit.controller.js | 1 + .../views/prevalueeditors/requiredfield.html | 1 + src/Umbraco.Web/Editors/DataTypeController.cs | 7 +++ .../ContentEditing/PreValueFieldDisplay.cs | 6 +++ .../ContentPropertyDisplayConverter.cs | 14 +++--- .../Mapping/ContentPropertyDtoConverter.cs | 14 ++++-- .../Mapping/ContentPropertyModelMapper.cs | 10 ++-- .../Models/Mapping/DataTypeModelMapper.cs | 9 +++- .../Models/Mapping/PreValueDisplayResolver.cs | 46 ++++++++++++++++++- 10 files changed, 92 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index b4b3f42422..f879510c00 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -146,6 +146,8 @@ namespace Umbraco.Core.Models } } + //NOTE: SD: Why do we have this ?? + /// /// Boolean indicating whether this entity is Trashed or not. /// diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js index caaa332b10..4496d24fc7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js @@ -33,6 +33,7 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc description: preVals[i].description, label: preVals[i].label, view: preVals[i].view, + value: preVals[i].value }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html index 621a0cb9d7..79b44ced00 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html @@ -4,4 +4,5 @@ required /> Required + diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 41dbed0c26..60a48b3934 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -55,5 +55,12 @@ namespace Umbraco.Web.Editors return propEd.PreValueEditor.Fields.Select(Mapper.Map); } + + //TODO: Generally there probably won't be file uploads for pre-values but we should allow them just like we do for the content editor + + public DataTypeDisplay PostSave() + { + return null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs index 9f38f6c331..772beeb727 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs @@ -37,5 +37,11 @@ namespace Umbraco.Web.Models.ContentEditing /// [DataMember(Name = "view", IsRequired = true)] public string View { get; set; } + + /// + /// The value stored for the pre-value field + /// + [DataMember(Name = "value", IsRequired = true)] + public string Value { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 8214d08262..1eb883e1eb 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; @@ -12,11 +13,11 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter { - private readonly ApplicationContext _applicationContext; + private readonly Lazy _dataTypeService; - public ContentPropertyDisplayConverter(ApplicationContext applicationContext) + public ContentPropertyDisplayConverter(Lazy dataTypeService) { - _applicationContext = applicationContext; + _dataTypeService = dataTypeService; } protected override ContentPropertyDisplay ConvertCore(Property originalProp) @@ -27,12 +28,11 @@ namespace Umbraco.Web.Models.Mapping display.Alias = originalProp.Alias; display.Description = originalProp.PropertyType.Description; display.Label = originalProp.PropertyType.Name; - var dataTypeService = (DataTypeService) _applicationContext.Services.DataTypeService; + + var dataTypeService = (DataTypeService)_dataTypeService.Value; var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); - - if (display.PropertyEditor == null) { display.Config = PreValueCollection.AsDictionary(preVals); diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 297406e10d..7c2310375b 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -1,6 +1,8 @@ -using Umbraco.Core; +using System; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping @@ -10,22 +12,24 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter { - private readonly ApplicationContext _applicationContext; + private readonly Lazy _dataTypeService; - public ContentPropertyDtoConverter(ApplicationContext applicationContext) + public ContentPropertyDtoConverter(Lazy dataTypeService) { - _applicationContext = applicationContext; + _dataTypeService = dataTypeService; } protected override ContentPropertyDto ConvertCore(Property originalProperty) { var propertyDto = base.ConvertCore(originalProperty); + var dataTypeService = (DataTypeService)_dataTypeService.Value; + propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; propertyDto.Description = originalProperty.PropertyType.Description; propertyDto.Label = originalProperty.PropertyType.Name; - propertyDto.DataType = _applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); + propertyDto.DataType = dataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); return propertyDto; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs index f567010f73..9ebb27098c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs @@ -1,7 +1,9 @@ -using AutoMapper; +using System; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping @@ -14,6 +16,8 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { + var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); + //FROM Property TO ContentPropertyBasic config.CreateMap>() .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) @@ -26,11 +30,11 @@ namespace Umbraco.Web.Models.Mapping //FROM Property TO ContentPropertyDto config.CreateMap() - .ConvertUsing(new ContentPropertyDtoConverter(applicationContext)); + .ConvertUsing(new ContentPropertyDtoConverter(lazyDataTypeService)); //FROM Property TO ContentPropertyDisplay config.CreateMap() - .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext)); + .ConvertUsing(new ContentPropertyDisplayConverter(lazyDataTypeService)); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index e3eb42897b..bee8624739 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -1,8 +1,10 @@ -using AutoMapper; +using System; +using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping @@ -11,6 +13,8 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { + var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); + config.CreateMap() .ForMember(basic => basic.EditorId, expression => expression.MapFrom(editor => editor.Id)); @@ -18,7 +22,8 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing()) - .ForMember(display => display.PreValues, expression => expression.ResolveUsing()) + .ForMember(display => display.PreValues, expression => expression.ResolveUsing( + new PreValueDisplayResolver(lazyDataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom(definition => definition.ControlId)); } } diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index 5bef6c75bd..a52822fed7 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -2,14 +2,24 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { internal class PreValueDisplayResolver : ValueResolver> { + private readonly Lazy _dataTypeService; + + public PreValueDisplayResolver(Lazy dataTypeService) + { + _dataTypeService = dataTypeService; + } + protected override IEnumerable ResolveCore(IDataTypeDefinition source) { var propEd = PropertyEditorResolver.Current.GetById(source.ControlId); @@ -18,7 +28,41 @@ namespace Umbraco.Web.Models.Mapping throw new InvalidOperationException("Could not find property editor with id " + source.ControlId); } - return propEd.PreValueEditor.Fields.Select(Mapper.Map); + var dataTypeService = (DataTypeService) _dataTypeService.Value; + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); + var dictionaryVals = PreValueCollection.AsDictionary(preVals); + + var result = propEd.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); + var currentIndex = 0; //used if the collection is non-dictionary based. + foreach (var field in result) + { + if (preVals.IsDictionaryBased == false) + { + //we'll just need to wire up the values based on the order that the pre-values are stored + var found = dictionaryVals.Any(x => x.Key.InvariantEquals(currentIndex.ToInvariantString())); + if (found == false) + { + LogHelper.Warn("Could not find persisted pre-value for index " + currentIndex); + continue; + } + field.Value = (string) dictionaryVals.Single(x => x.Key.InvariantEquals(currentIndex.ToInvariantString())).Value; + currentIndex++; + } + else + { + var found = dictionaryVals.Any(x => x.Key.InvariantEquals(field.Key)); + if (found == false) + { + LogHelper.Warn("Could not find persisted pre-value for field " + field.Key); + continue; + } + field.Value = (string)dictionaryVals.Single(x => x.Key.InvariantEquals(field.Key)).Value; + } + + + } + + return result; } } } \ No newline at end of file From 949748a7f7baa6a49158aea29ec86df66b896b54 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Aug 2013 14:44:59 +1000 Subject: [PATCH 2/6] Added some overloads to the data type service to be able to save data types + pre-values in one transaction, and also to be able to update pre-values (before we could only insert). Got the data type editor now validating and saving values back to the db. Updated the content views (content/media) to have a correct form structure with ng-submit working. --- .../UnitOfWork/PetaPocoUnitOfWork.cs | 65 +++++++++----- .../PropertyEditors/PreValueEditor.cs | 6 +- .../PropertyEditors/PropertyEditor.cs | 6 -- .../PropertyEditors/ValidatorBase.cs | 7 +- src/Umbraco.Core/Services/DataTypeService.cs | 89 +++++++++++++++++++ .../directives/valshowvalidation.directive.js | 1 - .../src/common/resources/datatype.resource.js | 11 ++- .../common/services/notifications.service.js | 4 +- .../src/common/services/util.service.js | 19 ++++ .../views/content/content.edit.controller.js | 17 ++-- .../src/views/content/edit.html | 11 ++- .../datatype/datatype.edit.controller.js | 14 +-- .../src/views/datatype/edit.html | 9 +- .../src/views/media/edit.html | 9 +- .../src/views/media/media.edit.controller.js | 10 +-- src/Umbraco.Web/Editors/DataTypeController.cs | 88 +++++++++++++++++- .../Models/ContentEditing/DataTypeDisplay.cs | 13 ++- .../Models/ContentEditing/DataTypeSave.cs | 29 ++++++ .../ContentEditing/PreValueFieldDisplay.cs | 15 +--- .../ContentEditing/PreValueFieldSave.cs | 23 +++++ .../Models/Mapping/DataTypeModelMapper.cs | 16 ++++ .../Models/Mapping/DatabaseTypeResolver.cs | 24 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 3 + .../WebApi/Binders/ContentItemBaseBinder.cs | 15 ++-- .../Filters/ContentItemValidationHelper.cs | 2 +- 25 files changed, 409 insertions(+), 97 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs create mode 100644 src/Umbraco.Web/Models/ContentEditing/PreValueFieldSave.cs create mode 100644 src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs index 4cdf4de85e..eb070ae4e6 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs @@ -93,31 +93,50 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// public void Commit() { - using (var transaction = Database.GetTransaction()) - { - foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) - { - switch (operation.Type) - { - case TransactionType.Insert: - operation.Repository.PersistNewItem(operation.Entity); - break; - case TransactionType.Delete: - operation.Repository.PersistDeletedItem(operation.Entity); - break; - case TransactionType.Update: - operation.Repository.PersistUpdatedItem(operation.Entity); - break; - } - } - transaction.Complete(); - } - - // Clear everything - _operations.Clear(); - _key = Guid.NewGuid(); + Commit(null); } + /// + /// Commits all batched changes within the scope of a PetaPoco transaction + /// + /// + /// Allows you to set a callback which is executed before the transaction is committed, allow you to add additional SQL + /// operations to the overall commit process after the queue has been processed. + /// + internal void Commit(Action transactionCompleting) + { + using (var transaction = Database.GetTransaction()) + { + foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) + { + switch (operation.Type) + { + case TransactionType.Insert: + operation.Repository.PersistNewItem(operation.Entity); + break; + case TransactionType.Delete: + operation.Repository.PersistDeletedItem(operation.Entity); + break; + case TransactionType.Update: + operation.Repository.PersistUpdatedItem(operation.Entity); + break; + } + } + + //Execute the callback if there is one + if (transactionCompleting != null) + { + transactionCompleting(Database); + } + + transaction.Complete(); + } + + // Clear everything + _operations.Clear(); + _key = Guid.NewGuid(); + } + public object Key { get { return _key; } diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs index c3917e2227..be006a71d7 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.PropertyEditors { public PreValueEditor() { - Fields = Enumerable.Empty(); + Fields = Enumerable.Empty(); } /// @@ -27,7 +27,7 @@ namespace Umbraco.Core.PropertyEditors /// If fields are specified then the master View and Validators will be ignored /// [JsonProperty("fields")] - public IEnumerable Fields { get; set; } - + public IEnumerable Fields { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index e86915bb49..ae71b5c7a6 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -74,12 +74,6 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("defaultConfig")] public virtual IDictionary DefaultPreValues { get; set; } - //TODO: Now we need to implement a couple of methods for saving the data for editors and pre-value editors - // generally we can handle that automatically in this base class but people should be allowed to override - // it so they can perform custom operations on saving the data. - // This also means that we should have methods for formatting the data upon retreival from the repository - // essentially we need serialization and deserialization methods to override. - /// /// Creates a value editor instance /// diff --git a/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs b/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs index c75d1eb2d6..16bb4b8c6e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs +++ b/src/Umbraco.Core/PropertyEditors/ValidatorBase.cs @@ -14,9 +14,12 @@ namespace Umbraco.Core.PropertyEditors /// /// Depending on what is being validated, this value can be a json structure representing an editor's model, it could be a single /// string representing an editor's model, this class structure is also used to validate pre-values and in that case this value - /// could be a json structure representing the entire pre-value model or it could ba a single value representing a pre-value field. + /// could be a json structure or a single value representing a pre-value field. + /// + /// + /// When validating a property editor value (not a pre-value), this is the current pre-values stored for the data type. + /// When validating a pre-value field, this is the name of that field. /// - /// The current pre-values stored for the data type /// The property editor instance that we are validating for /// public abstract IEnumerable Validate(string value, string preValues, PropertyEditor editor); diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 3a1e2e8f46..e5a7002037 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -224,6 +224,8 @@ namespace Umbraco.Core.Services /// List of string values to save public void SavePreValues(int id, IEnumerable values) { + //TODO: Should we raise an event here since we are really saving values for the data type? + using (new WriteLock(Locker)) { using (var uow = _uowProvider.GetUnitOfWork()) @@ -252,6 +254,93 @@ namespace Umbraco.Core.Services } } + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We will actually just remove all pre-values and re-insert them in one transaction + /// + internal void SavePreValues(int id, PreValueCollection values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (new WriteLock(Locker)) + { + using (var uow = _uowProvider.GetUnitOfWork()) + { + using (var transaction = uow.Database.GetTransaction()) + { + uow.Database.Execute("DELETE FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = id }); + + var sortOrder = 1; + foreach (var value in PreValueCollection.AsDictionary(values)) + { + var dto = new DataTypePreValueDto + { + DataTypeNodeId = id, + Value = (string)value.Value, + SortOrder = sortOrder, + Alias = value.Key + }; + uow.Database.Insert(dto); + sortOrder++; + } + + transaction.Complete(); + } + } + } + } + + /// + /// This will save a data type and it's pre-values in one transaction + /// + /// + /// + /// + internal void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, PreValueCollection values, int userId = 0) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) + return; + + using (new WriteLock(Locker)) + { + var uow = (PetaPocoUnitOfWork)_uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) + { + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + + //complete the transaction, but run the delegate before the db transaction is finalized + uow.Commit(database => + { + //Execute this before the transaction is completed! + database.Execute("DELETE FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeDefinition.Id }); + + var sortOrder = 1; + foreach (var value in PreValueCollection.AsDictionary(values)) + { + var dto = new DataTypePreValueDto + { + DataTypeNodeId = dataTypeDefinition.Id, + Value = (string)value.Value, + SortOrder = sortOrder, + Alias = value.Key + }; + database.Insert(dto); + sortOrder++; + } + }); + + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); + } + } + + Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + } + /// /// Deletes an /// diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valshowvalidation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/valshowvalidation.directive.js index 3cd94116b1..85a4748a85 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valshowvalidation.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/valshowvalidation.directive.js @@ -8,7 +8,6 @@ **/ function valShowValidation(serverValidationManager) { return { - require: "ngController", restrict: "A", link: function (scope, element, attr, ctrl) { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js index 291710f53a..89e23b5e31 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/datatype.resource.js @@ -43,13 +43,12 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** saves or updates a data type object */ - save: function (dataType, isNew) { + save: function (dataType, preValues, isNew) { + + var saveModel = umbDataFormatter.formatDataTypePostData(dataType, preValues, "save" + (isNew ? "New" : "")); + return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), - { - //TODO: SD: I need to finish this on Monday! - action: "save" + (isNew ? "New" : "") - }), + $http.post(umbRequestHelper.getApiUrl("dataTypeApiBaseUrl", "PostSave"), saveModel), 'Failed to save data for data type id ' + dataType.id); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js index 643b276a87..d0ae580921 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/notifications.service.js @@ -74,9 +74,7 @@ angular.module('umbraco.services') if (!args.header) { throw "args.header cannot be null"; } - if (!args.message) { - throw "args.message cannot be null"; - } + switch(args.type) { case 0: //save diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 96c1f22497..5556e3403f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -284,6 +284,25 @@ angular.module('umbraco.services').factory('umbImageHelper', umbImageHelper); function umbDataFormatter() { return { + /** formats the display model used to display the data type to the model used to save the data type */ + formatDataTypePostData: function(displayModel, preValues, action) { + var saveModel = { + id: displayModel.id, + name: displayModel.name, + selectedEditor: displayModel.selectedEditor, + //set the action on the save model + action: action, + preValues: [] + }; + for (var i = 0; i < preValues.length; i++) { + saveModel.preValues.push({ + key: preValues[i].alias, + value: preValues[i].value + }); + } + return saveModel; + }, + /** formats the display model used to display the content to the model used to save the content */ formatContentPostData: function (displayModel, action) { //NOTE: the display model inherits from the save model so we can in theory just post up the display model but diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index e033657124..8358dea7b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -43,20 +43,19 @@ function ContentEditController($scope, $routeParams, $location, contentResource, } }; - //ensure there is a form object assigned. - var currentForm = angularHelper.getRequiredCurrentForm($scope); - //TODO: Need to figure out a way to share the saving and event broadcasting with all editors! - $scope.saveAndPublish = function (cnt) { + $scope.saveAndPublish = function () { $scope.$broadcast("saving", { scope: $scope }); - + + var currentForm = angularHelper.getRequiredCurrentForm($scope); + //don't continue if the form is invalid if (currentForm.$invalid) return; serverValidationManager.reset(); - contentResource.publish(cnt, $routeParams.create, $scope.files) + contentResource.publish($scope.content, $routeParams.create, $scope.files) .then(function (data) { contentEditingHelper.handleSuccessfulSave({ @@ -70,15 +69,17 @@ function ContentEditController($scope, $routeParams, $location, contentResource, }); }; - $scope.save = function (cnt) { + $scope.save = function () { $scope.$broadcast("saving", { scope: $scope }); + var currentForm = angularHelper.getRequiredCurrentForm($scope); + //don't continue if the form is invalid if (currentForm.$invalid) return; serverValidationManager.reset(); - contentResource.save(cnt, $routeParams.create, $scope.files) + contentResource.save($scope.content, $routeParams.create, $scope.files) .then(function (data) { contentEditingHelper.handleSuccessfulSave({ diff --git a/src/Umbraco.Web.UI.Client/src/views/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/content/edit.html index fc32829941..64835d40c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/edit.html @@ -1,5 +1,8 @@ -
- + + @@ -16,7 +19,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js index 4496d24fc7..544a9133b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js @@ -66,9 +66,6 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc }); } - //ensure there is a form object assigned. - var currentForm = angularHelper.getRequiredCurrentForm($scope); - $scope.$watch("content.selectedEditor", function (newVal, oldVal) { //when the value changes, we need to dynamically load in the new editor if (newVal && oldVal && newVal != oldVal) { @@ -82,15 +79,18 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc } }); - $scope.save = function (cnt) { + $scope.save = function () { $scope.$broadcast("saving", { scope: $scope }); - + + //ensure there is a form object assigned. + var currentForm = angularHelper.getRequiredCurrentForm($scope); + //don't continue if the form is invalid if (currentForm.$invalid) return; serverValidationManager.reset(); - - dataTypeResource.save(cnt, $routeParams.create) + + dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create) .then(function (data) { contentEditingHelper.handleSuccessfulSave({ diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html index 87987bc464..3a4f3d2dda 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html @@ -1,5 +1,8 @@ - - + + @@ -13,7 +16,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/edit.html b/src/Umbraco.Web.UI.Client/src/views/media/edit.html index 8f031bb0b0..a1121e7f32 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/edit.html @@ -1,5 +1,8 @@ - - + + @@ -9,7 +12,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 8559f2a988..aa16731fde 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -42,20 +42,18 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS $scope.files.push({ id: propertyId, file: files[i] }); } }; - - //ensure there is a form object assigned. - var currentForm = angularHelper.getRequiredCurrentForm($scope); - - $scope.save = function (cnt) { + + $scope.save = function () { $scope.$broadcast("saving", { scope: $scope }); + var currentForm = angularHelper.getRequiredCurrentForm($scope); //don't continue if the form is invalid if (currentForm.$invalid) return; serverValidationManager.reset(); - mediaResource.save(cnt, $routeParams.create, $scope.files) + mediaResource.save($scope.content, $routeParams.create, $scope.files) .then(function (data) { contentEditingHelper.handleSuccessfulSave({ diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 60a48b3934..91ff59eecd 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -2,13 +2,18 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Web.Http; +using System.Web.Http.ModelBinding; using AutoMapper; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Binders; using Umbraco.Web.WebApi.Filters; +using umbraco; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -58,9 +63,88 @@ namespace Umbraco.Web.Editors //TODO: Generally there probably won't be file uploads for pre-values but we should allow them just like we do for the content editor - public DataTypeDisplay PostSave() + /// + /// Saves the data type + /// + /// + /// + public DataTypeDisplay PostSave(DataTypeSave dataType) { - return null; + //Validate that the property editor exists + var propertyEditor = PropertyEditorResolver.Current.GetById(dataType.SelectedEditor); + if (propertyEditor == null) + { + var message = string.Format("Property editor with id: {0} was not found", dataType.SelectedEditor); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, message)); + } + + //Validate the data type exists or create one if required + IDataTypeDefinition persisted; + switch (dataType.Action) + { + case ContentSaveAction.Save: + persisted = ApplicationContext.Services.DataTypeService.GetDataTypeDefinitionById(dataType.Id); + if (persisted == null) + { + var message = string.Format("Data type with id: {0} was not found", dataType.Id); + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, message)); + } + //map the model to the persisted instance + Mapper.Map(dataType, persisted); + break; + case ContentSaveAction.SaveNew: + //create the persisted model from mapping the saved model + persisted = Mapper.Map(dataType); + break; + default: + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //Validate each field + foreach (var preVal in dataType.PreValues) + { + var postedValue = preVal.Value; + + foreach (var v in propertyEditor.PreValueEditor.Fields.SelectMany(x => x.Validators)) + { + foreach (var result in v.Validate(postedValue, preVal.Key, propertyEditor)) + { + //if there are no member names supplied then we assume that the validation message is for the overall property + // not a sub field on the property editor + if (!result.MemberNames.Any()) + { + //add a model state error for the entire property + ModelState.AddModelError(string.Format("{0}.{1}", "Properties", preVal.Key), result.ErrorMessage); + } + else + { + //there's assigned field names so we'll combine the field name with the property name + // so that we can try to match it up to a real sub field of this editor + foreach (var field in result.MemberNames) + { + ModelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", preVal.Key, field), result.ErrorMessage); + } + } + } + } + } + + if (ModelState.IsValid == false) + { + //if it is not valid, do not continue and return the model state + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Forbidden, ModelState)); + } + + //finally we need to save the data type and it's pre-vals + var dtService = (DataTypeService) ApplicationContext.Services.DataTypeService; + var preVals = Mapper.Map(dataType.PreValues); + dtService.SaveDataTypeAndPreValues(persisted, preVals, (int)Security.CurrentUser.Id); + + var display = Mapper.Map(persisted); + display.AddSuccessNotification(ui.Text("speechBubbles", "dataTypeSaved"), ""); + + //now return the updated model + return display; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs index f52bd623d2..a19d8e93d5 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs @@ -9,8 +9,13 @@ namespace Umbraco.Web.Models.ContentEditing /// Represents a data type that is being edited ///
[DataContract(Name = "dataType", Namespace = "")] - public class DataTypeDisplay : EntityBasic + public class DataTypeDisplay : EntityBasic, INotificationModel { + public DataTypeDisplay() + { + Notifications = new List(); + } + [DataMember(Name = "selectedEditor", IsRequired = true)] [Required] public Guid SelectedEditor { get; set; } @@ -20,5 +25,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "preValues")] public IEnumerable PreValues { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs new file mode 100644 index 0000000000..3652f776b6 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "dataType", Namespace = "")] + public class DataTypeSave : EntityBasic + { + /// + /// The action to perform when saving this data type + /// + /// + /// If either of the Publish actions are specified an exception will be thrown. + /// + [DataMember(Name = "action", IsRequired = true)] + [Required] + public ContentSaveAction Action { get; set; } + + [DataMember(Name = "selectedEditor", IsRequired = true)] + [Required] + public Guid SelectedEditor { get; set; } + + [DataMember(Name = "preValues")] + public IEnumerable PreValues { get; set; } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs index 772beeb727..8d779ca23b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldDisplay.cs @@ -5,8 +5,8 @@ namespace Umbraco.Web.Models.ContentEditing /// /// Defines a pre value editable field for a data type /// - [DataContract(Name = "propertyEditor", Namespace = "")] - public class PreValueFieldDisplay + [DataContract(Name = "preValue", Namespace = "")] + public class PreValueFieldDisplay : PreValueFieldSave { /// /// The name to display for this pre-value field @@ -26,22 +26,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } - /// - /// The key to store the pre-value against - /// - [DataMember(Name = "key", IsRequired = true)] - public string Key { get; set; } - /// /// The view to render for the field /// [DataMember(Name = "view", IsRequired = true)] public string View { get; set; } - /// - /// The value stored for the pre-value field - /// - [DataMember(Name = "value", IsRequired = true)] - public string Value { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PreValueFieldSave.cs b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldSave.cs new file mode 100644 index 0000000000..e13a5dcf24 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PreValueFieldSave.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Defines a pre value editable field for a data type + /// + [DataContract(Name = "preValue", Namespace = "")] + public class PreValueFieldSave + { + /// + /// The key to store the pre-value against + /// + [DataMember(Name = "key", IsRequired = true)] + public string Key { get; set; } + + /// + /// The value stored for the pre-value field + /// + [DataMember(Name = "value", IsRequired = true)] + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index bee8624739..0e2e9b448b 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using AutoMapper; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Mapping; @@ -9,6 +12,9 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { + /// + /// Configure's model mappings for Data types + /// internal class DataTypeModelMapper : MapperConfiguration { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) @@ -25,6 +31,16 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.PreValues, expression => expression.ResolveUsing( new PreValueDisplayResolver(lazyDataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom(definition => definition.ControlId)); + + config.CreateMap() + .ConstructUsing(save => new DataTypeDefinition(-1, save.SelectedEditor) {CreateDate = DateTime.Now}) + .ForMember(definition => definition.ControlId, expression => expression.MapFrom(save => save.SelectedEditor)) + .ForMember(definition => definition.ParentId, expression => expression.MapFrom(save => -1)) + .ForMember(definition => definition.DatabaseType, expression => expression.ResolveUsing()); + + config.CreateMap, PreValueCollection>() + .ConstructUsing( + saves => new PreValueCollection(saves.ToDictionary(x => x.Key, x => (object) x.Value))); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs b/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs new file mode 100644 index 0000000000..3142689b4a --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/DatabaseTypeResolver.cs @@ -0,0 +1,24 @@ +using System; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Gets the DataTypeDatabaseType from the selected property editor for the data type + /// + internal class DatabaseTypeResolver : ValueResolver + { + protected override DataTypeDatabaseType ResolveCore(DataTypeSave source) + { + var propertyEditor = PropertyEditorResolver.Current.GetById(source.SelectedEditor); + if (propertyEditor == null) + { + throw new InvalidOperationException("Could not find property editor with id " + source.SelectedEditor); + } + return propertyEditor.ValueEditor.GetDatabaseType(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 903e4df729..e1948163dd 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -301,11 +301,14 @@ + + + diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs index f0b805ae22..e7805adaaa 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBaseBinder.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.WebApi.Binders /// Binds the content model to the controller action for the posted multi-part Post /// internal abstract class ContentItemBaseBinder : IModelBinder - where TPersisted : IContentBase + where TPersisted : class, IContentBase { protected ApplicationContext ApplicationContext { get; private set; } @@ -168,10 +168,15 @@ namespace Umbraco.Web.WebApi.Binders } //create the dto from the persisted model - model.ContentDto = MapFromPersisted(model); - - //now map all of the saved values to the dto - MapPropertyValuesFromSaved(model, model.ContentDto); + if (model.PersistedContent != null) + { + model.ContentDto = MapFromPersisted(model); + } + if (model.ContentDto != null) + { + //now map all of the saved values to the dto + MapPropertyValuesFromSaved(model, model.ContentDto); + } return model; } diff --git a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs index f8a2e4e958..f77d9a8910 100644 --- a/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/ContentItemValidationHelper.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.WebApi.Filters /// errors will just be added to the ModelState. /// internal class ContentItemValidationHelper - where TPersisted : IContentBase + where TPersisted : class, IContentBase { private readonly ApplicationContext _applicationContext; From ddef2cc7ff079022fff7c526acd7f22168537821 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Aug 2013 16:02:10 +1000 Subject: [PATCH 3/6] Got validation working properly with pre-values, updated the content editing helper to be more generic and work with different editors like the data type editor that supports umb-property. --- .../PropertyEditors/RequiredValueValidator.cs | 4 +- .../services/contenteditinghelper.service.js | 47 +++++++++++-------- .../services/umbrequesthelper.service.js | 8 ++-- .../views/content/content.edit.controller.js | 23 +++++++-- .../datatype/datatype.edit.controller.js | 12 ++++- .../src/views/media/media.edit.controller.js | 15 +++++- .../views/prevalueeditors/requiredfield.html | 4 +- .../services/content-editing-helper.spec.js | 36 ++++++++++---- .../ContentEditing/ContentItemDisplayBase.cs | 4 +- 9 files changed, 110 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/RequiredValueValidator.cs b/src/Umbraco.Core/PropertyEditors/RequiredValueValidator.cs index 7990998d9e..e40ae002d6 100644 --- a/src/Umbraco.Core/PropertyEditors/RequiredValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/RequiredValueValidator.cs @@ -15,11 +15,11 @@ namespace Umbraco.Core.PropertyEditors if (value == null) { - yield return new ValidationResult("Value cannot be null"); + yield return new ValidationResult("Value cannot be null", new[] { "value" }); } if (value.IsNullOrWhiteSpace()) { - yield return new ValidationResult("Value cannot be empty"); + yield return new ValidationResult("Value cannot be empty", new[] { "value" }); } } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index bf480be7a3..db9e05dc22 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -38,14 +38,10 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser * @description * re-binds all changed property values to the origContent object from the newContent object and returns an array of changed properties. */ - reBindChangedProperties: function (origContent, newContent) { + reBindChangedProperties: function (allOrigProps, allNewProps) { var changed = []; - //get a list of properties since they are contained in tabs - var allOrigProps = this.getAllProps(origContent); - var allNewProps = this.getAllProps(newContent); - function getNewProp(alias) { if (alias.startsWith("_umb_")) { return null; @@ -78,10 +74,8 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser * It's worth noting that when a 403 occurs, the data is still saved just never published, though this depends on if the entity is a new * entity and whether or not the data fulfils the absolute basic requirements like having a mandatory Name. */ - handleValidationErrors: function (content, modelState) { - //get a list of properties since they are contained in tabs - var allProps = this.getAllProps(content); - + handleValidationErrors: function (allProps, modelState) { + //find the content property for the current error, for use in the loop below function findContentProp(props, propAlias) { return _.find(props, function (item) { @@ -132,21 +126,36 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser * @description * A function to handle what happens when we have validation issues from the server side */ - handleSaveError: function (err, scope) { + handleSaveError: function (args) { + + if (!args.err) { + throw "args.err cannot be null"; + } + if (!args.allNewProps && !angular.isArray(args.allNewProps)) { + throw "args.allNewProps must be a valid array"; + } + if (!args.allOrigProps && !angular.isArray(args.allOrigProps)) { + throw "args.allOrigProps must be a valid array"; + } + //When the status is a 403 status, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). //Or, some strange server error - if (err.status === 403) { + if (args.err.status === 403) { //now we need to look through all the validation errors - if (err.data && (err.data.modelState)) { + if (args.err.data && (args.err.data.ModelState)) { + + this.handleValidationErrors(args.allNewProps, args.err.data.ModelState); - this.handleValidationErrors(err.data, err.data.modelState); - - if (!this.redirectToCreatedContent(err.data.id, err.data.modelState)) { + if (!this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { //we are not redirecting because this is not new content, it is existing content. In this case // we need to detect what properties have changed and re-bind them with the server data. Then we need // to re-bind any server validation errors after the digest takes place. - this.reBindChangedProperties(scope.content, err.data); + + if (args.rebindCallback && angular.isFunction(args.rebindCallback)) { + args.rebindCallback(); + } + serverValidationManager.executeAndClearAllSubscriptions(); } @@ -154,11 +163,11 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser return true; } else { - dialogService.ysodDialog(err); + dialogService.ysodDialog(args.err); } } else { - dialogService.ysodDialog(err); + dialogService.ysodDialog(args.err); } return false; @@ -166,7 +175,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser /** * @ngdoc function - * @name umbraco.services.contentEditingHelper#handleSaveError + * @name umbraco.services.contentEditingHelper#handleSuccessfulSave * @methodOf umbraco.services.contentEditingHelper * @function * diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index a260ce904e..9b52da35ad 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -85,7 +85,7 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ * In both of the above, the successCallback must accept these parameters: data, status, headers, config * If using the errorCallback it must accept these parameters: data, status, headers, config * The success callback must return the data which will be resolved by the deferred object. - * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData } + * The error callback must return an object containing: {errorMsg: errorMessage, data: originalData, status: status } */ resourcePromise: function (httpPromise, opts) { var deferred = $q.defer(); @@ -101,7 +101,8 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ return { //NOTE: the default error message here should never be used based on the above docs! errorMsg: (angular.isString(opts) ? opts : 'An error occurred!'), - data: data + data: data, + status: status }; } @@ -137,7 +138,8 @@ function umbRequestHelper($http, $q, umbDataFormatter, angularHelper, dialogServ //return an error object including the error message for UI deferred.reject({ errorMsg: result.errorMsg, - data: result.data + data: result.data, + status: result.status }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 8358dea7b8..7e290796dc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -61,11 +61,22 @@ function ContentEditController($scope, $routeParams, $location, contentResource, contentEditingHelper.handleSuccessfulSave({ scope: $scope, newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties(scope.content, data) + rebindCallback: contentEditingHelper.reBindChangedProperties( + contentEditingHelper.getAllProps($scope.content), + contentEditingHelper.getAllProps(data)) }); - }, function (err) { - contentEditingHelper.handleSaveError(err, $scope); + }, function (err) { + + var allNewProps = contentEditingHelper.getAllProps(err.data); + var allOrigProps = contentEditingHelper.getAllProps($scope.content); + + contentEditingHelper.handleSaveError({ + err: err, + allNewProps: allNewProps, + allOrigProps: contentEditingHelper.getAllProps($scope.content), + rebindCallback: contentEditingHelper.reBindChangedProperties(allOrigProps, allNewProps) + }); }); }; @@ -89,7 +100,11 @@ function ContentEditController($scope, $routeParams, $location, contentResource, }); }, function (err) { - contentEditingHelper.handleSaveError(err, $scope); + contentEditingHelper.handleSaveError({ + err: err, + allNewProps: contentEditingHelper.getAllProps(err.data), + allOrigProps: contentEditingHelper.getAllProps($scope.content) + }); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js index 544a9133b8..72e52d835c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js @@ -102,7 +102,17 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc }); }, function (err) { - contentEditingHelper.handleSaveError(err, $scope); + + //NOTE: in the case of data type values we are setting the orig/new props + // to be the same thing since that only really matters for content/media. + contentEditingHelper.handleSaveError({ + err: err, + allNewProps: $scope.preValues, + allOrigProps: $scope.preValues, + rebindCallback: function () { + createPreValueProps(err.data.preValues); + } + }); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index aa16731fde..088d0ee4a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -59,11 +59,22 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS contentEditingHelper.handleSuccessfulSave({ scope: $scope, newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties(scope.content, data) + rebindCallback: contentEditingHelper.reBindChangedProperties( + contentEditingHelper.getAllProps($scope.content), + contentEditingHelper.getAllProps(data)) }); }, function (err) { - contentEditingHelper.handleSaveError(err, $scope); + + var allNewProps = contentEditingHelper.getAllProps(err.data); + var allOrigProps = contentEditingHelper.getAllProps($scope.content); + + contentEditingHelper.handleSaveError({ + err: err, + allNewProps: allNewProps, + allOrigProps: contentEditingHelper.getAllProps($scope.content), + rebindCallback: contentEditingHelper.reBindChangedProperties(allOrigProps, allNewProps) + }); }); }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html index 79b44ced00..5012926855 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html @@ -1,8 +1,8 @@ 
+ val-server="value"/> Required - + {{propertyForm.requiredField.errorMsg}}
diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js index 1778cd34c2..3aa47a7eb5 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js @@ -22,10 +22,14 @@ describe('contentEditingHelper tests', function () { data: content, status: 403 }; - err.data.modelState = {}; + err.data.ModelState = {}; //act - var handled = contentEditingHelper.handleSaveError(err, {content: content}); + var handled = contentEditingHelper.handleSaveError({ + err: err, + allNewProps: contentEditingHelper.getAllProps(content), + allOrigProps: contentEditingHelper.getAllProps(content) + }); //assert expect(handled).toBe(true); @@ -39,7 +43,11 @@ describe('contentEditingHelper tests', function () { }; //act - var handled = contentEditingHelper.handleSaveError(err, null); + var handled = contentEditingHelper.handleSaveError({ + err: err, + allNewProps: [], + allOrigProps: [] + }); //assert expect(handled).toBe(false); @@ -55,7 +63,11 @@ describe('contentEditingHelper tests', function () { }; //act - var handled = contentEditingHelper.handleSaveError(err, { content: content }); + var handled = contentEditingHelper.handleSaveError({ + err: err, + allNewProps: contentEditingHelper.getAllProps(content), + allOrigProps: contentEditingHelper.getAllProps(content) + }); //assert expect(handled).toBe(false); @@ -69,9 +81,10 @@ describe('contentEditingHelper tests', function () { //arrange var content = mocksUtils.getMockContent(1234); + var allProps = contentEditingHelper.getAllProps(content); //act - contentEditingHelper.handleValidationErrors(content, { Name: ["Required"] }); + contentEditingHelper.handleValidationErrors(allProps, { Name: ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -84,9 +97,10 @@ describe('contentEditingHelper tests', function () { //arrange var content = mocksUtils.getMockContent(1234); + var allProps = contentEditingHelper.getAllProps(content); //act - contentEditingHelper.handleValidationErrors(content, { "Property.bodyText": ["Required"] }); + contentEditingHelper.handleValidationErrors(allProps, { "Property.bodyText": ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -99,9 +113,10 @@ describe('contentEditingHelper tests', function () { //arrange var content = mocksUtils.getMockContent(1234); + var allProps = contentEditingHelper.getAllProps(content); //act - contentEditingHelper.handleValidationErrors(content, { "Property.bodyText.value": ["Required"] }); + contentEditingHelper.handleValidationErrors(allProps, { "Property.bodyText.value": ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -114,10 +129,11 @@ describe('contentEditingHelper tests', function () { //arrange var content = mocksUtils.getMockContent(1234); + var allProps = contentEditingHelper.getAllProps(content); //act contentEditingHelper.handleValidationErrors( - content, + allProps, { "Name": ["Required"], "UpdateDate": ["Invalid date"], @@ -202,7 +218,9 @@ describe('contentEditingHelper tests', function () { newContent.tabs[1].properties[2].value = "origValue4"; //act - var changed = contentEditingHelper.reBindChangedProperties(origContent, newContent); + var changed = contentEditingHelper.reBindChangedProperties( + contentEditingHelper.getAllProps(origContent), + contentEditingHelper.getAllProps(newContent)); //assert expect(changed.length).toBe(2); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs index 676c694793..dfdd08757b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplayBase.cs @@ -32,8 +32,10 @@ namespace Umbraco.Web.Models.ContentEditing /// 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. + /// + /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. /// - [DataMember(Name = "modelState")] + [DataMember(Name = "ModelState")] public IDictionary Errors { get; set; } } } From b901ae326971cddbc288570882e08400f64e5aaf Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Aug 2013 17:25:17 +1000 Subject: [PATCH 4/6] Updated the data type controller post method and moved validation, etc... over to a filter, updated data type tree with the create menu action to launch into a blank element. --- .../common/services/menuactions.service.js | 4 +- .../datatype/datatype.edit.controller.js | 5 +- src/Umbraco.Web/Editors/DataTypeController.cs | 71 +---------- .../Editors/DataTypeValidateAttribute.cs | 119 ++++++++++++++++++ .../Models/ContentEditing/DataTypeSave.cs | 15 +++ .../Trees/ContentTreeController.cs | 4 +- .../Trees/DataTypeTreeController.cs | 4 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 2 +- .../Trees/Menu/CreateChildEntity.cs | 10 ++ ...{RefreshNodeMenuItem.cs => RefreshNode.cs} | 18 +-- src/Umbraco.Web/Umbraco.Web.csproj | 4 +- src/umbraco.cms/Actions/ActionRefresh.cs | 2 +- 12 files changed, 169 insertions(+), 89 deletions(-) create mode 100644 src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs create mode 100644 src/Umbraco.Web/Trees/Menu/CreateChildEntity.cs rename src/Umbraco.Web/Trees/Menu/{RefreshNodeMenuItem.cs => RefreshNode.cs} (72%) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js index 302834194c..583678b86f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js @@ -14,7 +14,7 @@ function umbracoMenuActions($q, treeService, $location) { /** * @ngdoc method - * @name umbraco.services.umbracoMenuActions#RefreshNodeMenuItem + * @name umbraco.services.umbracoMenuActions#RefreshNode * @methodOf umbraco.services.umbracoMenuActions * @function * @@ -24,7 +24,7 @@ function umbracoMenuActions($q, treeService, $location) { * @param {object} args.treeNode The tree node * @param {object} args.section The current section */ - "RefreshNodeMenuItem": function (args) { + "RefreshNode": function (args) { treeService.loadNodeChildren({ node: args.treeNode, section: args.section }); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js index 72e52d835c..a6ac7b319e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js @@ -108,10 +108,7 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc contentEditingHelper.handleSaveError({ err: err, allNewProps: $scope.preValues, - allOrigProps: $scope.preValues, - rebindCallback: function () { - createPreValueProps(err.data.preValues); - } + allOrigProps: $scope.preValues }); }); }; diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 91ff59eecd..ba66776276 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Http; using System.Web.Http; using System.Web.Http.ModelBinding; using AutoMapper; @@ -68,79 +67,17 @@ namespace Umbraco.Web.Editors /// /// /// + [DataTypeValidate] public DataTypeDisplay PostSave(DataTypeSave dataType) { - //Validate that the property editor exists - var propertyEditor = PropertyEditorResolver.Current.GetById(dataType.SelectedEditor); - if (propertyEditor == null) - { - var message = string.Format("Property editor with id: {0} was not found", dataType.SelectedEditor); - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, message)); - } - - //Validate the data type exists or create one if required - IDataTypeDefinition persisted; - switch (dataType.Action) - { - case ContentSaveAction.Save: - persisted = ApplicationContext.Services.DataTypeService.GetDataTypeDefinitionById(dataType.Id); - if (persisted == null) - { - var message = string.Format("Data type with id: {0} was not found", dataType.Id); - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, message)); - } - //map the model to the persisted instance - Mapper.Map(dataType, persisted); - break; - case ContentSaveAction.SaveNew: - //create the persisted model from mapping the saved model - persisted = Mapper.Map(dataType); - break; - default: - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //Validate each field - foreach (var preVal in dataType.PreValues) - { - var postedValue = preVal.Value; - - foreach (var v in propertyEditor.PreValueEditor.Fields.SelectMany(x => x.Validators)) - { - foreach (var result in v.Validate(postedValue, preVal.Key, propertyEditor)) - { - //if there are no member names supplied then we assume that the validation message is for the overall property - // not a sub field on the property editor - if (!result.MemberNames.Any()) - { - //add a model state error for the entire property - ModelState.AddModelError(string.Format("{0}.{1}", "Properties", preVal.Key), result.ErrorMessage); - } - else - { - //there's assigned field names so we'll combine the field name with the property name - // so that we can try to match it up to a real sub field of this editor - foreach (var field in result.MemberNames) - { - ModelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", preVal.Key, field), result.ErrorMessage); - } - } - } - } - } - - if (ModelState.IsValid == false) - { - //if it is not valid, do not continue and return the model state - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.Forbidden, ModelState)); - } + //If we've made it here, then everything has been wired up and validated by the attribute //finally we need to save the data type and it's pre-vals var dtService = (DataTypeService) ApplicationContext.Services.DataTypeService; var preVals = Mapper.Map(dataType.PreValues); - dtService.SaveDataTypeAndPreValues(persisted, preVals, (int)Security.CurrentUser.Id); + dtService.SaveDataTypeAndPreValues(dataType.PersistedDataType, preVals, (int)Security.CurrentUser.Id); - var display = Mapper.Map(persisted); + var display = Mapper.Map(dataType.PersistedDataType); display.AddSuccessNotification(ui.Text("speechBubbles", "dataTypeSaved"), ""); //now return the updated model diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs new file mode 100644 index 0000000000..07b839eabe --- /dev/null +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -0,0 +1,119 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors +{ + /// + /// An action filter that wires up the persisted entity of the DataTypeSave model and validates the whole request + /// + internal sealed class DataTypeValidateAttribute : ActionFilterAttribute + { + private readonly IDataTypeService _dataTypeService; + + public DataTypeValidateAttribute() + { + } + + public DataTypeValidateAttribute(IDataTypeService dataTypeService) + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + _dataTypeService = dataTypeService; + } + + private IDataTypeService DataTypeService + { + get { return _dataTypeService ?? ApplicationContext.Current.Services.DataTypeService; } + } + + public override void OnActionExecuting(HttpActionContext actionContext) + { + var dataType = (DataTypeSave)actionContext.ActionArguments["dataType"]; + + //Validate that the property editor exists + var propertyEditor = PropertyEditorResolver.Current.GetById(dataType.SelectedEditor); + if (propertyEditor == null) + { + var message = string.Format("Property editor with id: {0} was not found", dataType.SelectedEditor); + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + return; + } + + //assign the prop editor to the model + dataType.PropertyEditor = propertyEditor; + + //Validate the data type exists or create one if required + IDataTypeDefinition persisted; + switch (dataType.Action) + { + case ContentSaveAction.Save: + persisted = DataTypeService.GetDataTypeDefinitionById(dataType.Id); + if (persisted == null) + { + var message = string.Format("Data type with id: {0} was not found", dataType.Id); + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + return; + } + //map the model to the persisted instance + Mapper.Map(dataType, persisted); + break; + case ContentSaveAction.SaveNew: + //create the persisted model from mapping the saved model + persisted = Mapper.Map(dataType); + break; + default: + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //now assign the persisted entity to the model so we can use it in the action + dataType.PersistedDataType = persisted; + + //Validate each field + foreach (var preVal in dataType.PreValues) + { + var postedValue = preVal.Value; + + foreach (var v in propertyEditor.PreValueEditor.Fields.SelectMany(x => x.Validators)) + { + foreach (var result in v.Validate(postedValue, preVal.Key, propertyEditor)) + { + //if there are no member names supplied then we assume that the validation message is for the overall property + // not a sub field on the property editor + if (!result.MemberNames.Any()) + { + //add a model state error for the entire property + actionContext.ModelState.AddModelError(string.Format("{0}.{1}", "Properties", preVal.Key), result.ErrorMessage); + } + else + { + //there's assigned field names so we'll combine the field name with the property name + // so that we can try to match it up to a real sub field of this editor + foreach (var field in result.MemberNames) + { + actionContext.ModelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", preVal.Key, field), result.ErrorMessage); + } + } + } + } + } + + if (actionContext.ModelState.IsValid == false) + { + //if it is not valid, do not continue and return the model state + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, actionContext.ModelState); + return; + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs index 3652f776b6..4215932716 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeSave.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; +using Newtonsoft.Json; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.Models.ContentEditing { @@ -25,5 +28,17 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "preValues")] public IEnumerable PreValues { get; set; } + /// + /// The real persisted data type + /// + [JsonIgnore] + internal IDataTypeDefinition PersistedDataType { get; set; } + + /// + /// The PropertyEditor assigned + /// + [JsonIgnore] + internal PropertyEditor PropertyEditor { get; set; } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 27e787575e..b5a450638f 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -111,7 +111,7 @@ namespace Umbraco.Web.Trees // add default actions for *all* users allowedMenu.AddMenuItem(); - allowedMenu.AddMenuItem(true); + allowedMenu.AddMenuItem(true); return allowedMenu; } @@ -155,7 +155,7 @@ namespace Umbraco.Web.Trees menu.AddMenuItem(true); menu.AddMenuItem(); - menu.AddMenuItem(true); + menu.AddMenuItem(true); return menu; } diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 73477e0f71..5907f89fe9 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -45,8 +45,8 @@ namespace Umbraco.Web.Trees if (id == Constants.System.Root.ToInvariantString()) { // root actions - menu.AddMenuItem(); - menu.AddMenuItem(true); + menu.AddMenuItem(); + menu.AddMenuItem(true); return menu; } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 7078e7d2ac..95a39fd3c6 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees // root actions menu.AddMenuItem(); menu.AddMenuItem(true); - menu.AddMenuItem(true); + menu.AddMenuItem(true); return menu; } diff --git a/src/Umbraco.Web/Trees/Menu/CreateChildEntity.cs b/src/Umbraco.Web/Trees/Menu/CreateChildEntity.cs new file mode 100644 index 0000000000..8a26e12aed --- /dev/null +++ b/src/Umbraco.Web/Trees/Menu/CreateChildEntity.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Web.Trees.Menu +{ + /// + /// Represents the refresh node menu item + /// + [ActionMenuItem("umbracoMenuActions")] + public sealed class CreateChildEntity : ActionMenuItem + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/Menu/RefreshNodeMenuItem.cs b/src/Umbraco.Web/Trees/Menu/RefreshNode.cs similarity index 72% rename from src/Umbraco.Web/Trees/Menu/RefreshNodeMenuItem.cs rename to src/Umbraco.Web/Trees/Menu/RefreshNode.cs index 0eb78a0eb5..5f1d06bf4e 100644 --- a/src/Umbraco.Web/Trees/Menu/RefreshNodeMenuItem.cs +++ b/src/Umbraco.Web/Trees/Menu/RefreshNode.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Web.Trees.Menu -{ - /// - /// Represents the refresh node menu item - /// - [ActionMenuItem("umbracoMenuActions")] - public sealed class RefreshNodeMenuItem : ActionMenuItem - { - } +namespace Umbraco.Web.Trees.Menu +{ + /// + /// Represents the refresh node menu item + /// + [ActionMenuItem("umbracoMenuActions")] + public sealed class RefreshNode : ActionMenuItem + { + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e1948163dd..651a651cdc 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -299,6 +299,7 @@ + @@ -320,6 +321,7 @@ + @@ -379,7 +381,7 @@ - + diff --git a/src/umbraco.cms/Actions/ActionRefresh.cs b/src/umbraco.cms/Actions/ActionRefresh.cs index 9aa0a4fe78..e94d4d2fed 100644 --- a/src/umbraco.cms/Actions/ActionRefresh.cs +++ b/src/umbraco.cms/Actions/ActionRefresh.cs @@ -9,7 +9,7 @@ namespace umbraco.BusinessLogic.Actions /// Concerns only the tree itself and thus you should not handle /// this action from without umbraco. /// - [LegacyActionMenuItem("umbracoMenuActions", "RefreshNodeMenuItem")] + [LegacyActionMenuItem("umbracoMenuActions", "RefreshNode")] public class ActionRefresh : IAction { //create singleton From 7df641cf2b767eba66722466a549ae5cd2182794 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Aug 2013 18:10:19 +1000 Subject: [PATCH 5/6] Got Data Type Creation working. Need to look at fixing up the issues with creating content/media tomorrow. --- .../services/contenteditinghelper.service.js | 4 +-- .../common/services/menuactions.service.js | 6 +++- .../views/content/content.edit.controller.js | 1 + .../datatype/datatype.edit.controller.js | 35 +++++++++---------- .../src/views/media/media.edit.controller.js | 1 + .../views/prevalueeditors/requiredfield.html | 3 +- src/Umbraco.Web/Editors/DataTypeController.cs | 6 ++++ .../Editors/DataTypeValidateAttribute.cs | 5 ++- .../Models/ContentEditing/DataTypeDisplay.cs | 5 ++- .../Models/Mapping/DataTypeModelMapper.cs | 3 +- .../Models/Mapping/PreValueDisplayResolver.cs | 17 ++++++--- 11 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index db9e05dc22..10bb81b4d4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -147,7 +147,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser this.handleValidationErrors(args.allNewProps, args.err.data.ModelState); - if (!this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { + if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { //we are not redirecting because this is not new content, it is existing content. In this case // we need to detect what properties have changed and re-bind them with the server data. Then we need // to re-bind any server validation errors after the digest takes place. @@ -204,7 +204,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser } args.scope.$broadcast("saved", { scope: args.scope }); - if (!this.redirectToCreatedContent(args.scope.content.id)) { + if (!this.redirectToCreatedContent(args.newContent.id)) { //we are not redirecting because this is not new content, it is existing content. In this case // we need to detect what properties have changed and re-bind them with the server data. diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js index 583678b86f..28bc971096 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js @@ -8,7 +8,7 @@ * @description * Defines the methods that are called when menu items declare only an action to execute */ -function umbracoMenuActions($q, treeService, $location) { +function umbracoMenuActions($q, treeService, $location, navigationService) { return { @@ -41,9 +41,13 @@ function umbracoMenuActions($q, treeService, $location) { * @param {object} args.section The current section */ "CreateChildEntity": function (args) { + + navigationService.hideNavigation(); + var route = "/" + args.section + "/" + treeService.getTreeAlias(args.treeNode) + "/edit/" + args.treeNode.id; //change to new path $location.path(route).search({ create: true }); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 7e290796dc..1c15c89b08 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -73,6 +73,7 @@ function ContentEditController($scope, $routeParams, $location, contentResource, contentEditingHelper.handleSaveError({ err: err, + redirectOnFailure: true, allNewProps: allNewProps, allOrigProps: contentEditingHelper.getAllProps($scope.content), rebindCallback: contentEditingHelper.reBindChangedProperties(allOrigProps, allNewProps) diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js index a6ac7b319e..206f4c4a50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/datatype.edit.controller.js @@ -8,22 +8,7 @@ */ function DataTypeEditController($scope, $routeParams, $location, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) { - //set up the standard data type props - function createDisplayProps() { - $scope.properties = { - selectedEditor: { - alias: "selectedEditor", - description: "Select a property editor", - label: "Property editor" - }, - selectedEditorId: { - alias: "selectedEditorId", - label: "Property editor GUID" - } - }; - } - - //setup the pre-values as props + //method used to configure the pre-values when we retreive them from the server function createPreValueProps(preVals) { $scope.preValues = []; for (var i = 0; i < preVals.length; i++) { @@ -38,6 +23,22 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc } } + //set up the standard data type props + $scope.properties = { + selectedEditor: { + alias: "selectedEditor", + description: "Select a property editor", + label: "Property editor" + }, + selectedEditorId: { + alias: "selectedEditorId", + label: "Property editor GUID" + } + }; + + //setup the pre-values as props + $scope.preValues = []; + if ($routeParams.create) { //we are creating so get an empty content item dataTypeResource.getScaffold($routeParams.id, $routeParams.doctype) @@ -45,7 +46,6 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc $scope.loaded = true; $scope.preValuesLoaded = true; $scope.content = data; - createDisplayProps(); }); } else { @@ -55,7 +55,6 @@ function DataTypeEditController($scope, $routeParams, $location, dataTypeResourc $scope.loaded = true; $scope.preValuesLoaded = true; $scope.content = data; - createDisplayProps(); createPreValueProps($scope.content.preValues); //in one particular special case, after we've created a new item we redirect back to the edit diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 088d0ee4a5..18ccc0aaf9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -71,6 +71,7 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS contentEditingHelper.handleSaveError({ err: err, + redirectOnFailure: true, allNewProps: allNewProps, allOrigProps: contentEditingHelper.getAllProps($scope.content), rebindCallback: contentEditingHelper.reBindChangedProperties(allOrigProps, allNewProps) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html index 5012926855..14c06d29d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/requiredfield.html @@ -1,7 +1,8 @@ 
+ required + val-server="value" /> Required {{propertyForm.requiredField.errorMsg}} diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index ba66776276..f84fd9b48a 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -44,6 +44,12 @@ namespace Umbraco.Web.Editors return Mapper.Map(dataType); } + public DataTypeDisplay GetEmpty() + { + var dt = new DataTypeDefinition(-1, Guid.Empty); + return Mapper.Map(dt); + } + /// /// Returns the pre-values for the specified property editor /// diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs index 07b839eabe..1d6d2f5b12 100644 --- a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -8,6 +8,7 @@ using System.Web.Http.Filters; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -70,9 +71,11 @@ namespace Umbraco.Web.Editors case ContentSaveAction.SaveNew: //create the persisted model from mapping the saved model persisted = Mapper.Map(dataType); + ((DataTypeDefinition)persisted).ResetIdentity(); break; default: - throw new HttpResponseException(HttpStatusCode.NotFound); + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new ArgumentOutOfRangeException()); + return; } //now assign the persisted entity to the model so we can use it in the action diff --git a/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs index a19d8e93d5..4c9e159595 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DataTypeDisplay.cs @@ -16,9 +16,12 @@ namespace Umbraco.Web.Models.ContentEditing Notifications = new List(); } + /// + /// This is nullable because when we are creating a new one, it is nothing + /// [DataMember(Name = "selectedEditor", IsRequired = true)] [Required] - public Guid SelectedEditor { get; set; } + public Guid? SelectedEditor { get; set; } [DataMember(Name = "availableEditors")] public IEnumerable AvailableEditors { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index 0e2e9b448b..0f4a53f9e2 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -30,7 +30,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing()) .ForMember(display => display.PreValues, expression => expression.ResolveUsing( new PreValueDisplayResolver(lazyDataTypeService))) - .ForMember(display => display.SelectedEditor, expression => expression.MapFrom(definition => definition.ControlId)); + .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( + definition => definition.ControlId == Guid.Empty ? null : (Guid?) definition.ControlId)); config.CreateMap() .ConstructUsing(save => new DataTypeDefinition(-1, save.SelectedEditor) {CreateDate = DateTime.Now}) diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index a52822fed7..f89047a83d 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -22,17 +22,26 @@ namespace Umbraco.Web.Models.Mapping protected override IEnumerable ResolveCore(IDataTypeDefinition source) { - var propEd = PropertyEditorResolver.Current.GetById(source.ControlId); - if (propEd == null) + PropertyEditor propEd = null; + if (source.ControlId != Guid.Empty) { - throw new InvalidOperationException("Could not find property editor with id " + source.ControlId); + propEd = PropertyEditorResolver.Current.GetById(source.ControlId); + if (propEd == null) + { + throw new InvalidOperationException("Could not find property editor with id " + source.ControlId); + } } + var dataTypeService = (DataTypeService) _dataTypeService.Value; var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); var dictionaryVals = PreValueCollection.AsDictionary(preVals); - var result = propEd.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); + var result = Enumerable.Empty(); + if (propEd != null) + { + result = propEd.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); + } var currentIndex = 0; //used if the collection is non-dictionary based. foreach (var field in result) { From e71fa681e608ba0ea65b0e2601b5496c1870b3a2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 20 Aug 2013 14:18:02 +0200 Subject: [PATCH 6/6] Update build to v7 and include belle files --- build/Build.bat | 3 +-- build/Build.proj | 13 ++++++++++++- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index d1335ea93e..ff11f0e7ab 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -1,5 +1,5 @@ @ECHO OFF -SET release=6.1.4 +SET release=7.0.0 SET comment= SET version=%release% @@ -20,7 +20,6 @@ echo This file is only here so that the containing folder will be included in th echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\usercontrols\dummy.txt echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\Views\Partials\dummy.txt echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\Views\MacroPartials\dummy.txt -echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\xslt\dummy.txt ..\src\.nuget\NuGet.exe pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% ..\src\.nuget\NuGet.exe pack NuSpecs\UmbracoCms.nuspec -Version %version% diff --git a/build/Build.proj b/build/Build.proj index 5ceeadb141..6e9b2a453d 100644 --- a/build/Build.proj +++ b/build/Build.proj @@ -162,10 +162,21 @@ - + + + + + + + + + diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 9863edfee3..e15e365ba4 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("6.1.4"); + private static readonly Version Version = new Version("7.0.0"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 1ae6479358..463e421a13 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2627,9 +2627,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 6140 + 7000 / - http://localhost:6140 + http://localhost:7000 False False