From 3e675ff2cce79353a5793fab10d5772a415b8660 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Aug 2013 19:24:20 +1000 Subject: [PATCH] Converted over pre-value config to be a dictionary, not an array. To do that I needed to create a new PreValueCollection object which encapsulates support for legacy (array based) pre-vals which get converted to a dictionary and newer dictionary pre-vals... which we will now require because we'll support pre-value overrides eventually. Fixed up the mapper unit tests and added other unit tests. --- src/Umbraco.Core/Models/PreValueCollection.cs | 64 +++++++++++++ .../ObjectResolution/Resolution.cs | 30 ++++--- .../PropertyEditors/PropertyEditor.cs | 57 +++++++++++- .../PropertyEditors/ValueEditor.cs | 14 ++- src/Umbraco.Core/Services/DataTypeService.cs | 52 +++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../ContentModelSerializationTests.cs | 5 +- .../Mapping/ContentWebModelMappingTests.cs | 31 +++++-- .../PropertyEditorValueConverterTests.cs | 3 +- .../PropertyEditorValueEditorTests.cs | 72 +++++++++++++++ .../Services/DataTypeServiceTests.cs | 2 + .../Services/PreValueConverterTests.cs | 90 +++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +- .../directives/umbtreeitem.directive.js | 4 +- .../datepicker/datepicker.html | 7 +- src/Umbraco.Web/Editors/ContentController.cs | 4 +- .../ContentEditing/ContentItemDisplay.cs | 17 ---- .../ContentEditing/ContentPropertyDisplay.cs | 2 +- .../ContentEditing/ContentPropertyDto.cs | 1 - .../Models/ContentEditing/TemplateBasic.cs | 21 +++++ .../Mapping/ContentPropertyBasicConverter.cs | 5 ++ .../ContentPropertyDisplayConverter.cs | 12 ++- .../Mapping/ContentPropertyDtoConverter.cs | 2 - .../PropertyEditors/DatePropertyEditor.cs | 22 +++++ .../PropertyEditors/DateTimePropertyEditor.cs | 32 +++++++ .../PropertyEditors/DateTimeValidator.cs | 28 ++++++ src/Umbraco.Web/Umbraco.Web.csproj | 4 + src/umbraco.businesslogic/ui.cs | 45 ++++++---- 28 files changed, 557 insertions(+), 74 deletions(-) create mode 100644 src/Umbraco.Core/Models/PreValueCollection.cs create mode 100644 src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs create mode 100644 src/Umbraco.Tests/Services/PreValueConverterTests.cs create mode 100644 src/Umbraco.Web/Models/ContentEditing/TemplateBasic.cs create mode 100644 src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs create mode 100644 src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs create mode 100644 src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs diff --git a/src/Umbraco.Core/Models/PreValueCollection.cs b/src/Umbraco.Core/Models/PreValueCollection.cs new file mode 100644 index 0000000000..154e4a58ad --- /dev/null +++ b/src/Umbraco.Core/Models/PreValueCollection.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + /// + /// Represents the pre-value data for a DataType + /// + /// + /// Due to the legacy nature of the data that can be stored for pre-values, we have this class which encapsulates the 2 different + /// ways that pre-values are stored: A string array or a Dictionary. + /// + /// Most legacy property editors won't support the dictionary format but new property editors should always use the dictionary format. + /// In order to get overrideable pre-values working we need a dictionary since we'll have to reference a pre-value by a key. + /// + public class PreValueCollection + { + private IDictionary _preValuesAsDictionary; + private IEnumerable _preValuesAsArray; + public IEnumerable PreValuesAsArray + { + get + { + if (_preValuesAsArray == null) + { + throw new InvalidOperationException("The current pre-value collection is dictionary based, use the PreValuesAsDictionary property instead"); + } + return _preValuesAsArray; + } + set { _preValuesAsArray = value; } + } + + public IDictionary PreValuesAsDictionary + { + get + { + if (_preValuesAsDictionary == null) + { + throw new InvalidOperationException("The current pre-value collection is array based, use the PreValuesAsArray property instead"); + } + return _preValuesAsDictionary; + } + set { _preValuesAsDictionary = value; } + } + + /// + /// Check if it is a dictionary based collection + /// + public bool IsDictionaryBased + { + get { return _preValuesAsDictionary != null; } + } + + public PreValueCollection(IEnumerable preVals) + { + _preValuesAsArray = preVals; + } + + public PreValueCollection(IDictionary preVals) + { + _preValuesAsDictionary = preVals; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs index d9e5977e4a..2c6dab4225 100644 --- a/src/Umbraco.Core/ObjectResolution/Resolution.cs +++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs @@ -13,8 +13,9 @@ namespace Umbraco.Core.ObjectResolution internal static class Resolution { private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(); + private volatile static bool _isFrozen; - /// + /// /// Occurs when resolution is frozen. /// /// Occurs only once, since resolution can be frozen only once. @@ -23,12 +24,17 @@ namespace Umbraco.Core.ObjectResolution /// /// Gets or sets a value indicating whether resolution of objects is frozen. /// - public static bool IsFrozen { get; private set; } - - public static void EnsureIsFrozen() + public static bool IsFrozen { - if (!IsFrozen) + get { return _isFrozen; } + private set { _isFrozen = value; } + } + + public static void EnsureIsFrozen() + { + if (!_isFrozen) throw new InvalidOperationException("Resolution is not frozen, it is not yet possible to get values from it."); + } /// @@ -40,7 +46,7 @@ namespace Umbraco.Core.ObjectResolution get { IDisposable l = new WriteLock(_lock); - if (Resolution.IsFrozen) + if (_isFrozen) { l.Dispose(); throw new InvalidOperationException("Resolution is frozen, it is not possible to configure it anymore."); @@ -73,13 +79,13 @@ namespace Umbraco.Core.ObjectResolution public DirtyBackdoor() { _lock = new WriteLock(_dirtyLock); - _frozen = Resolution.IsFrozen; - Resolution.IsFrozen = false; + _frozen = _isFrozen; + _isFrozen = false; } public void Dispose() { - Resolution.IsFrozen = _frozen; + _isFrozen = _frozen; _lock.Dispose(); } } @@ -90,10 +96,10 @@ namespace Umbraco.Core.ObjectResolution /// resolution is already frozen. public static void Freeze() { - if (Resolution.IsFrozen) + if (_isFrozen) throw new InvalidOperationException("Resolution is frozen. It is not possible to freeze it again."); - IsFrozen = true; + _isFrozen = true; if (Frozen != null) Frozen(null, null); } @@ -104,7 +110,7 @@ namespace Umbraco.Core.ObjectResolution /// To be used in unit tests. internal static void Reset() { - IsFrozen = false; + _isFrozen = false; Frozen = null; } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs index 650c41a613..3c23bc9991 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -28,14 +29,23 @@ namespace Umbraco.Core.PropertyEditors { Id = Guid.Parse(att.Id); Name = att.Name; - + StaticallyDefinedValueEditor.ValueType = att.ValueType; StaticallyDefinedValueEditor.View = att.EditorView; StaticallyDefinedPreValueEditor.View = att.PreValueEditorView; } } + /// + /// These are assigned by default normally based on property editor attributes or manifest definitions, + /// developers have the chance to override CreateValueEditor if they don't want to use the pre-defined instance + /// internal ValueEditor StaticallyDefinedValueEditor = null; + + /// + /// These are assigned by default normally based on property editor attributes or manifest definitions, + /// developers have the chance to override CreatePreValueEditor if they don't want to use the pre-defined instance + /// internal PreValueEditor StaticallyDefinedPreValueEditor = null; /// @@ -62,6 +72,9 @@ namespace Umbraco.Core.PropertyEditors get { return CreatePreValueEditor(); } } + [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. @@ -96,6 +109,48 @@ namespace Umbraco.Core.PropertyEditors return StaticallyDefinedPreValueEditor; } + /// + /// This can be used to re-format the currently saved pre-values that will be passed to the editor, + /// by default this returns the merged default and persisted pre-values. + /// + /// + /// The default/static pre-vals for the property editor + /// + /// + /// The persisted pre-vals for the property editor + /// + /// + /// + /// This is generally not going to be used by anything unless a property editor wants to change the merging + /// functionality or needs to convert some legacy persisted data, or something else ? + /// + public virtual IDictionary FormatPreValues(IDictionary defaultPreVals, PreValueCollection persistedPreVals) + { + if (defaultPreVals == null) + { + defaultPreVals = new Dictionary(); + } + + if (persistedPreVals.IsDictionaryBased) + { + //we just need to merge the dictionaries now, the persisted will replace default. + foreach (var item in persistedPreVals.PreValuesAsDictionary) + { + defaultPreVals[item.Key] = item.Value; + } + return defaultPreVals; + } + + //it's an array so need to format it + var result = new Dictionary(); + var asArray = persistedPreVals.PreValuesAsArray.ToArray(); + for (var i = 0; i < asArray.Length; i++) + { + result.Add(i.ToInvariantString(), asArray[i]); + } + return result; + } + protected bool Equals(PropertyEditor other) { return Id.Equals(other.Id); diff --git a/src/Umbraco.Core/PropertyEditors/ValueEditor.cs b/src/Umbraco.Core/PropertyEditors/ValueEditor.cs index 0cc3fe60a6..8f8f23a056 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueEditor.cs @@ -129,10 +129,12 @@ namespace Umbraco.Core.PropertyEditors valueType = typeof(string); break; case DataTypeDatabaseType.Integer: - valueType = typeof(int); + //ensure these are nullable so we can return a null if required + valueType = typeof(int?); break; case DataTypeDatabaseType.Date: - valueType = typeof(DateTime); + //ensure these are nullable so we can return a null if required + valueType = typeof(DateTime?); break; default: throw new ArgumentOutOfRangeException(); @@ -184,6 +186,14 @@ namespace Umbraco.Core.PropertyEditors //we can just ToString() any of these types return dbValue.ToString(); case DataTypeDatabaseType.Date: + var s = dbValue as string; + if (s != null) + { + if (s.IsNullOrWhiteSpace()) + { + return string.Empty; + } + } //Dates will be formatted in 'o' format (otherwise known as xml format) return dbValue.ToXmlString(); default: diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 68c0f308bd..3a2bc3a4f2 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -133,6 +133,22 @@ namespace Umbraco.Core.Services } } + /// + /// Returns the PreValueCollection for the specified data type + /// + /// + /// + internal PreValueCollection GetPreValuesCollectionByDataTypeId(int id) + { + using (var uow = _uowProvider.GetUnitOfWork()) + { + var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = id }); + var list = dtos.Select(x => new Tuple(x.Id, x.Alias, x.SortOrder, x.Value)).ToList(); + + return PreValueConverter.ConvertToPreValuesCollection(list); + } + } + /// /// Gets a specific PreValue by its Id /// @@ -327,5 +343,41 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> Saved; #endregion + + internal static class PreValueConverter + { + /// + /// Converts the tuple to a pre-value collection + /// + /// + /// + internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable> list) + { + //now we need to determine if they are dictionary based, otherwise they have to be array based + var dictionary = new Dictionary(); + + //need to check all of the keys, if there's only one and it is empty then it's an array + var keys = list.Select(x => x.Item2).Distinct().ToArray(); + if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace()) + { + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item4)); + } + + foreach (var item in list + .OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary + .GroupBy(x => x.Item2)) + { + if (item.Count() > 1) + { + //if there's more than 1 item per key, then it cannot be a dictionary, just return the array + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item4)); + } + + dictionary.Add(item.Key, item.First().Item4); + } + + return new PreValueCollection(dictionary); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ae5ccb1e60..0d67bbbdae 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -225,6 +225,7 @@ + diff --git a/src/Umbraco.Tests/AngularIntegration/ContentModelSerializationTests.cs b/src/Umbraco.Tests/AngularIntegration/ContentModelSerializationTests.cs index d33b9045e5..cee6bf00f8 100644 --- a/src/Umbraco.Tests/AngularIntegration/ContentModelSerializationTests.cs +++ b/src/Umbraco.Tests/AngularIntegration/ContentModelSerializationTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using NUnit.Framework; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Core; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Tests.AngularIntegration @@ -29,7 +30,7 @@ namespace Umbraco.Tests.AngularIntegration Label = "Property " + propertyIndex, Id = propertyIndex, Value = "value" + propertyIndex, - Config = new[] {"config" + propertyIndex}, + Config = new Dictionary {{ propertyIndex.ToInvariantString(), "value" }}, Description = "Description " + propertyIndex, View = "~/Views/View" + propertyIndex, HideLabel = false @@ -68,7 +69,7 @@ namespace Umbraco.Tests.AngularIntegration Assert.AreEqual("Property " + prop, jObject["tabs"][tab]["properties"][prop]["label"].ToString()); Assert.AreEqual(prop, jObject["tabs"][tab]["properties"][prop]["id"].Value()); Assert.AreEqual("value" + prop, jObject["tabs"][tab]["properties"][prop]["value"].ToString()); - Assert.AreEqual("[\"config" + prop + "\"]", jObject["tabs"][tab]["properties"][prop]["config"].ToString(Formatting.None)); + Assert.AreEqual("{\"" + prop + "\":\"value\"}", jObject["tabs"][tab]["properties"][prop]["config"].ToString(Formatting.None)); Assert.AreEqual("Description " + prop, jObject["tabs"][tab]["properties"][prop]["description"].ToString()); Assert.AreEqual(false, jObject["tabs"][tab]["properties"][prop]["hideLabel"].Value()); } diff --git a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs index b951380698..44d3149e85 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs @@ -31,8 +31,8 @@ namespace Umbraco.Tests.Models.Mapping protected override void FreezeResolution() { - PropertyEditorResolver.Current = new PropertyEditorResolver( - () => new List {typeof (TestPropertyEditor)}); + //PropertyEditorResolver.Current = new PropertyEditorResolver( + // () => new List {typeof (TestPropertyEditor)}); base.FreezeResolution(); } @@ -96,6 +96,13 @@ namespace Umbraco.Tests.Models.Mapping { var contentType = MockedContentTypes.CreateSimpleContentType(); var content = MockedContent.CreateSimpleContent(contentType); + //need ids for tabs + var id = 1; + foreach (var g in content.PropertyGroups) + { + g.Id = id; + id++; + } var result = Mapper.Map(content); @@ -131,6 +138,13 @@ namespace Umbraco.Tests.Models.Mapping p.Id = idSeed; idSeed++; } + //need ids for tabs + var id = 1; + foreach (var g in content.PropertyGroups) + { + g.Id = id; + id++; + } //ensure that nothing is marked as dirty contentType.ResetDirtyProperties(false); //ensure that nothing is marked as dirty @@ -145,7 +159,7 @@ namespace Umbraco.Tests.Models.Mapping } Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1); Assert.IsTrue(result.Tabs.Any(x => x.Label == "Generic properties")); - Assert.AreEqual(2, result.Tabs.Where(x => x.Label == "Generic properties").SelectMany(x => x.Properties).Count()); + Assert.AreEqual(2, result.Tabs.Where(x => x.Label == "Generic properties").SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count()); } #region Assertions @@ -158,10 +172,11 @@ namespace Umbraco.Tests.Models.Mapping var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias); Assert.IsNotNull(pDto); - pDto.Alias = p.Alias; - pDto.Description = p.PropertyType.Description; - pDto.Label = p.PropertyType.Name; - pDto.Config = applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(p.PropertyType.DataTypeDefinitionId); + + //pDto.Alias = p.Alias; + //pDto.Description = p.PropertyType.Description; + //pDto.Label = p.PropertyType.Name; + //pDto.Config = applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(p.PropertyType.DataTypeDefinitionId); } @@ -176,7 +191,7 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(content.UpdateDate, result.UpdateDate); Assert.AreEqual(content.CreateDate, result.CreateDate); Assert.AreEqual(content.Name, result.Name); - Assert.AreEqual(content.Properties.Count(), result.Properties.Count()); + Assert.AreEqual(content.Properties.Count(), result.Properties.Count(x => x.Alias.StartsWith("_umb_") == false)); } private void AssertBasicProperty(ContentItemBasic result, Property p) diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs index 4e5cfe56dd..989895f50d 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.PropertyEditors { - [TestFixture] + [TestFixture] public class PropertyEditorValueConverterTests { [TestCase("2012-11-10", true)] diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs new file mode 100644 index 0000000000..730dd4ad56 --- /dev/null +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -0,0 +1,72 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Tests.PropertyEditors +{ + [TestFixture] + public class PropertyEditorValueEditorTests + { + [TestCase("STRING", "hello", "hello")] + [TestCase("TEXT", "hello", "hello")] + [TestCase("INT", "123", 123)] + [TestCase("INTEGER", "123", 123)] + [TestCase("INTEGER", "", null)] //test empty string for int + [TestCase("DATETIME", "", null)] //test empty string for date + public void Value_Editor_Can_Convert_To_Clr_Type(string valueType, string val, object expected) + { + var valueEditor = new ValueEditor + { + ValueType = valueType + }; + + var result = valueEditor.TryConvertValueToCrlType(val); + Assert.IsTrue(result.Success); + Assert.AreEqual(expected, result.Result); + } + + [Test] + public void Value_Editor_Can_Convert_To_Date_Clr_Type() + { + var valueEditor = new ValueEditor + { + ValueType = "DATE" + }; + + var result = valueEditor.TryConvertValueToCrlType("2010-02-05"); + Assert.IsTrue(result.Success); + Assert.AreEqual(new DateTime(2010, 2, 5), result.Result); + } + + [TestCase("STRING", "hello", "hello")] + [TestCase("TEXT", "hello", "hello")] + [TestCase("INT", 123, "123")] + [TestCase("INTEGER", 123, "123")] + [TestCase("INTEGER", "", "")] //test empty string for int + [TestCase("DATETIME", "", "")] //test empty string for date + public void Value_Editor_Can_Serialize_Value(string valueType, object val, string expected) + { + var valueEditor = new ValueEditor + { + ValueType = valueType + }; + + var result = valueEditor.SerializeValue(val); + Assert.AreEqual(expected, result); + } + + [Test] + public void Value_Editor_Can_Serialize_Date_Value() + { + var now = DateTime.Now; + var valueEditor = new ValueEditor + { + ValueType = "DATE" + }; + + var result = valueEditor.SerializeValue(now); + Assert.AreEqual(now.ToXmlString(), result); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs index 31b0489949..259c28ba25 100644 --- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs @@ -23,6 +23,8 @@ namespace Umbraco.Tests.Services base.TearDown(); } + + [Test] public void DataTypeService_Can_Persist_New_DataTypeDefinition() { diff --git a/src/Umbraco.Tests/Services/PreValueConverterTests.cs b/src/Umbraco.Tests/Services/PreValueConverterTests.cs new file mode 100644 index 0000000000..d02544052b --- /dev/null +++ b/src/Umbraco.Tests/Services/PreValueConverterTests.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Services; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class PreValueConverterTests + { + [Test] + public void Can_Convert_To_Dictionary_Pre_Value_Collection() + { + var list = new List> + { + new Tuple(10, "key1", 0, "value1"), + new Tuple(11, "key2", 3, "value2"), + new Tuple(12, "key3", 2, "value3"), + new Tuple(13, "key4", 1, "value4") + }; + + var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + + Assert.Throws(() => + { + var blah = result.PreValuesAsArray; + }); + + Assert.AreEqual(4, result.PreValuesAsDictionary.Count); + Assert.AreEqual("key1", result.PreValuesAsDictionary.ElementAt(0).Key); + Assert.AreEqual("key4", result.PreValuesAsDictionary.ElementAt(1).Key); + Assert.AreEqual("key3", result.PreValuesAsDictionary.ElementAt(2).Key); + Assert.AreEqual("key2", result.PreValuesAsDictionary.ElementAt(3).Key); + + } + + [Test] + public void Can_Convert_To_Array_Pre_Value_Collection_When_Empty_Key() + { + var list = new List> + { + new Tuple(10, "", 0, "value1"), + new Tuple(11, "", 3, "value2"), + new Tuple(12, "", 2, "value3"), + new Tuple(13, "", 1, "value4") + }; + + var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + + Assert.Throws(() => + { + var blah = result.PreValuesAsDictionary; + }); + + Assert.AreEqual(4, result.PreValuesAsArray.Count()); + Assert.AreEqual("value1", result.PreValuesAsArray.ElementAt(0)); + Assert.AreEqual("value4", result.PreValuesAsArray.ElementAt(1)); + Assert.AreEqual("value3", result.PreValuesAsArray.ElementAt(2)); + Assert.AreEqual("value2", result.PreValuesAsArray.ElementAt(3)); + + } + + [Test] + public void Can_Convert_To_Array_Pre_Value_Collection() + { + var list = new List> + { + new Tuple(10, "key1", 0, "value1"), + new Tuple(11, "key1", 3, "value2"), + new Tuple(12, "key3", 2, "value3"), + new Tuple(13, "key4", 1, "value4") + }; + + var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + + Assert.Throws(() => + { + var blah = result.PreValuesAsDictionary; + }); + + Assert.AreEqual(4, result.PreValuesAsArray.Count()); + Assert.AreEqual("value1", result.PreValuesAsArray.ElementAt(0)); + Assert.AreEqual("value4", result.PreValuesAsArray.ElementAt(1)); + Assert.AreEqual("value3", result.PreValuesAsArray.ElementAt(2)); + Assert.AreEqual("value2", result.PreValuesAsArray.ElementAt(3)); + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 29abbe2015..a05aeee774 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -55,7 +55,7 @@ False ..\packages\AutoMapper.2.2.1\lib\net40\AutoMapper.dll - + False ..\packages\Examine.0.1.52.2941\lib\Examine.dll @@ -213,8 +213,10 @@ + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js index e349ea81d4..3311d5005e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbtreeitem.directive.js @@ -33,8 +33,8 @@ angular.module("umbraco.directives") template: '
  • ' + '' + '' + - '' + - '{{node.name}}' + + '' + + '{{node.name}}' + '' + '
    ' + '
    ' + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index 3c1b9e3372..10f1a715ee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -1,8 +1,11 @@
    - + - +
    + {{propertyForm.datepicker.errorMsg}}
    diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index c64e035b79..e18ca1c529 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -195,7 +195,9 @@ namespace Umbraco.Web.Editors contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; //only set the template if it didn't change - if (contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias); + if (templateChanged) { var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); if (template == null) diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index 57bfcbe006..df1182ba9b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.Serialization; using System.Web.Http; @@ -32,20 +31,4 @@ namespace Umbraco.Web.Models.ContentEditing public string[] Urls { get; set; } } - - [DataContract(Name = "template", Namespace = "")] - public class TemplateBasic - { - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } - - [DataMember(Name = "name", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string Name { get; set; } - - [DataMember(Name = "alias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string Alias { get; set; } - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index ac01601e27..a087840f34 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Models.ContentEditing public string View { get; set; } [DataMember(Name = "config")] - public IEnumerable Config { get; set; } + public IDictionary Config { get; set; } [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs index 42782b1b14..17f4f39680 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs @@ -12,7 +12,6 @@ namespace Umbraco.Web.Models.ContentEditing public IDataTypeDefinition DataType { get; set; } public string Label { get; set; } public string Description { get; set; } - public PropertyEditor PropertyEditor { get; set; } public bool IsRequired { get; set; } public string ValidationRegExp { get; set; } } diff --git a/src/Umbraco.Web/Models/ContentEditing/TemplateBasic.cs b/src/Umbraco.Web/Models/ContentEditing/TemplateBasic.cs new file mode 100644 index 0000000000..dbb3d6c8bd --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/TemplateBasic.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "template", Namespace = "")] + public class TemplateBasic + { + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } + + [DataMember(Name = "name", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string Name { get; set; } + + [DataMember(Name = "alias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string Alias { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index ac8bb18b8b..32313cf75e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -14,6 +14,11 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicConverter : TypeConverter where T : ContentPropertyBasic, new() { + /// + /// Assigns the PropertyEditor, Id, Alias and Value to the property + /// + /// + /// protected override T ConvertCore(Property property) { var editor = PropertyEditorResolver.Current.GetById(property.PropertyType.DataTypeId); diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index c51ac48ae5..e149b1c5ef 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -1,6 +1,8 @@ -using Umbraco.Core; +using System.Linq; +using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping @@ -25,7 +27,13 @@ namespace Umbraco.Web.Models.Mapping display.Alias = originalProp.Alias; display.Description = originalProp.PropertyType.Description; display.Label = originalProp.PropertyType.Name; - display.Config = _applicationContext.Services.DataTypeService.GetPreValuesByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); + var dataTypeService = (DataTypeService) _applicationContext.Services.DataTypeService; + + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); + + //let the property editor format the pre-values + display.Config = display.PropertyEditor.FormatPreValues(display.PropertyEditor.DefaultPreValues, preVals); + if (display.PropertyEditor == null) { //if there is no property editor it means that it is a legacy data type diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 9fe38cc988..297406e10d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -23,11 +23,9 @@ namespace Umbraco.Web.Models.Mapping propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; - propertyDto.Alias = originalProperty.Alias; propertyDto.Description = originalProperty.PropertyType.Description; propertyDto.Label = originalProperty.PropertyType.Name; propertyDto.DataType = _applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); - propertyDto.PropertyEditor = PropertyEditorResolver.Current.GetById(originalProperty.PropertyType.DataTypeId); return propertyDto; } diff --git a/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs new file mode 100644 index 0000000000..06458928f0 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DatePropertyEditor.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [PropertyEditor(Constants.PropertyEditors.Date, "Date", "datepicker", ValueType = "DATE")] + public class DatePropertyEditor : PropertyEditor + { + protected override ValueEditor CreateValueEditor() + { + var editor = base.CreateValueEditor(); + + editor.Validators = new List { new DateTimeValidator() }; + + return editor; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs new file mode 100644 index 0000000000..3ec863b7ab --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateTimePropertyEditor.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + [PropertyEditor(Constants.PropertyEditors.DateTime, "Date/Time", "datepicker", ValueType = "DATETIME")] + public class DateTimePropertyEditor : PropertyEditor + { + public DateTimePropertyEditor() + { + _defaultPreVals = new Dictionary() ; + } + + private IDictionary _defaultPreVals; + + public override IDictionary DefaultPreValues + { + get { return _defaultPreVals; } + set { _defaultPreVals = value; } + } + + protected override ValueEditor CreateValueEditor() + { + var editor = base.CreateValueEditor(); + + editor.Validators = new List { new DateTimeValidator() }; + + return editor; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs b/src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs new file mode 100644 index 0000000000..734e34622f --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/DateTimeValidator.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Used to validate if the value is a valid date/time + /// + internal class DateTimeValidator : ValidatorBase + { + public override IEnumerable Validate(string value, string preValues, PropertyEditor editor) + { + DateTime dt; + if (DateTime.TryParse(value, out dt) == false) + { + yield return new ValidationResult(string.Format("The string value {0} cannot be parsed into a DateTime", value), + new[] + { + //we only store a single value for this editor so the 'member' or 'field' + // we'll associate this error with will simply be called 'value' + "value" + }); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 40cdd91586..168657080a 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -299,7 +299,11 @@ + + + + diff --git a/src/umbraco.businesslogic/ui.cs b/src/umbraco.businesslogic/ui.cs index cfb0a1832d..4416ed2451 100644 --- a/src/umbraco.businesslogic/ui.cs +++ b/src/umbraco.businesslogic/ui.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Text.RegularExpressions; using System.Threading; using System.Web; @@ -308,27 +309,35 @@ namespace umbraco { var cacheKey = "uitext_" + language; - return ApplicationContext.Current.ApplicationCache.GetCacheItem( - cacheKey, - CacheItemPriority.Default, - new CacheDependency(IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml")), - () => - { - using (var langReader = new XmlTextReader(IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml"))) + var file = IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml"); + if (File.Exists(file)) + { + return ApplicationContext.Current.ApplicationCache.GetCacheItem( + cacheKey, + CacheItemPriority.Default, + new CacheDependency(IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml")), + () => { - try + using (var langReader = new XmlTextReader(IOHelper.MapPath(UmbracoPath + "/config/lang/" + language + ".xml"))) { - var langFile = new XmlDocument(); - langFile.Load(langReader); - return langFile; + try + { + var langFile = new XmlDocument(); + langFile.Load(langReader); + return langFile; + } + catch (Exception e) + { + LogHelper.Error("Error reading umbraco language xml source (" + language + ")", e); + return null; + } } - catch (Exception e) - { - LogHelper.Error("Error reading umbraco language xml source (" + language + ")", e); - return null; - } - } - }); + }); + } + else + { + return null; + } }