Got value editors being able to handle 'object' not just string so we can post and return json structures directly to the angular editors, got the multiple drop down list working and saving data. U4-2704 Change property editor value editor to return/receive object instead of string

This commit is contained in:
Shannon
2013-08-26 18:03:35 +10:00
parent 5b289a4971
commit 3708b0de65
21 changed files with 227 additions and 237 deletions

View File

@@ -262,7 +262,6 @@ namespace Umbraco.Core
{
new Lazy<Type>(() => typeof (RequiredValueValidator)),
new Lazy<Type>(() => typeof (RegexValueValidator)),
new Lazy<Type>(() => typeof (ValueTypeValueValidator)),
new Lazy<Type>(() => typeof (DelimitedValueValidator))
});

View File

@@ -16,7 +16,7 @@ namespace Umbraco.Core.Models.Editors
/// </remarks>
public class ContentPropertyData
{
public ContentPropertyData(string value, IDictionary<string, object> additionalData)
public ContentPropertyData(object value, IDictionary<string, object> additionalData)
{
Value = value;
AdditionalData = new ReadOnlyDictionary<string, object>(additionalData);
@@ -27,7 +27,7 @@ namespace Umbraco.Core.Models.Editors
/// <summary>
/// The string value submitted for the property
/// </summary>
public string Value { get; private set; }
public object Value { get; private set; }
/// <summary>
/// A dictionary containing any additional objects that are related to this property when saving

View File

@@ -21,45 +21,48 @@ namespace Umbraco.Core.PropertyEditors
/// <param name="preValues">The current pre-values stored for the data type</param>
/// <param name="editor"></param>
/// <returns></returns>
public override IEnumerable<ValidationResult> Validate(string value, string config, string preValues, PropertyEditor editor)
public override IEnumerable<ValidationResult> Validate(object value, string config, string preValues, PropertyEditor editor)
{
//TODO: localize these!
var delimiter = ",";
Regex regex = null;
if (config.IsNullOrWhiteSpace() == false)
if (value != null)
{
var json = JsonConvert.DeserializeObject<JObject>(config);
if (json["delimiter"] != null)
var delimiter = ",";
Regex regex = null;
if (config.IsNullOrWhiteSpace() == false)
{
delimiter = json["delimiter"].ToString();
}
if (json["pattern"] != null)
{
var regexPattern = json["pattern"].ToString();
regex = new Regex(regexPattern);
}
}
var stringVal = value;
var split = stringVal.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < split.Length; i++)
{
var s = split[i];
//next if we have a regex statement validate with that
if (regex != null)
{
if (regex.IsMatch(s) == false)
var json = JsonConvert.DeserializeObject<JObject>(config);
if (json["delimiter"] != null)
{
yield return new ValidationResult("The item at index " + i + " did not match the expression " + regex,
new[]
delimiter = json["delimiter"].ToString();
}
if (json["pattern"] != null)
{
var regexPattern = json["pattern"].ToString();
regex = new Regex(regexPattern);
}
}
var stringVal = value.ToString();
var split = stringVal.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < split.Length; i++)
{
var s = split[i];
//next if we have a regex statement validate with that
if (regex != null)
{
if (regex.IsMatch(s) == false)
{
yield return new ValidationResult("The item at index " + i + " did not match the expression " + regex,
new[]
{
//make the field name called 'value0' where 0 is the index
"value" + i
});
}
}
}
}
}
}
}

View File

@@ -56,7 +56,7 @@ namespace Umbraco.Core.PropertyEditors
/// <param name="preValues">The current pre-values stored for the data type</param>
/// <param name="editor">The property editor instance that we are validating for</param>
/// <returns></returns>
public override IEnumerable<ValidationResult> Validate(string value, string preValues, PropertyEditor editor)
public override IEnumerable<ValidationResult> Validate(object value, string preValues, PropertyEditor editor)
{
return ValidatorInstance.Validate(value, Config, preValues, editor);
}

View File

@@ -11,16 +11,21 @@ namespace Umbraco.Core.PropertyEditors
[ValueValidator("Regex")]
internal sealed class RegexValueValidator : ValueValidator
{
public override IEnumerable<ValidationResult> Validate(string value, string config, string preValues, PropertyEditor editor)
public override IEnumerable<ValidationResult> Validate(object value, string config, string preValues, PropertyEditor editor)
{
//TODO: localize these!
var regex = new Regex(config);
if (!regex.IsMatch(value))
if (config.IsNullOrWhiteSpace() == false && value != null)
{
yield return new ValidationResult("Value is invalid, it does not match the correct pattern", new[] {"value"});
}
var asString = value.ToString();
var regex = new Regex(config);
if (regex.IsMatch(asString) == false)
{
yield return new ValidationResult("Value is invalid, it does not match the correct pattern", new[] { "value" });
}
}
}
}
}

View File

@@ -9,18 +9,24 @@ namespace Umbraco.Core.PropertyEditors
[ValueValidator("Required")]
internal sealed class RequiredValueValidator : ValueValidator
{
public override IEnumerable<ValidationResult> Validate(string value, string config, string preValues, PropertyEditor editor)
public override IEnumerable<ValidationResult> Validate(object value, string config, string preValues, PropertyEditor editor)
{
//TODO: localize these!
if (value == null)
{
yield return new ValidationResult("Value cannot be null", new[] { "value" });
yield return new ValidationResult("Value cannot be null", new[] {"value"});
}
if (value.IsNullOrWhiteSpace())
else
{
yield return new ValidationResult("Value cannot be empty", new[] { "value" });
var asString = value.ToString();
if (asString.IsNullOrWhiteSpace())
{
yield return new ValidationResult("Value cannot be empty", new[] { "value" });
}
}
}
}
}

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.PropertyEditors
/// Validates the object with the resolved ValueValidator found for this type
/// </summary>
/// <param name="value">
/// Depending on what is being validated, this value can be a json structure representing an editor's model, it could be a single
/// Depending on what is being validated, this value can be a json structure (JObject, JArray, etc...) 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 or a single value representing a pre-value field.
/// </param>
@@ -22,6 +22,6 @@ namespace Umbraco.Core.PropertyEditors
/// </param>
/// <param name="editor">The property editor instance that we are validating for</param>
/// <returns></returns>
public abstract IEnumerable<ValidationResult> Validate(string value, string preValues, PropertyEditor editor);
public abstract IEnumerable<ValidationResult> Validate(object value, string preValues, PropertyEditor editor);
}
}

View File

@@ -121,7 +121,7 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
internal Attempt<object> TryConvertValueToCrlType(string value)
internal Attempt<object> TryConvertValueToCrlType(object value)
{
Type valueType;
//convert the string to a known type
@@ -174,12 +174,15 @@ namespace Umbraco.Core.PropertyEditors
//TODO: Change the result to object so we can pass back JSON or json converted clr types if we want!
/// <summary>
/// A method used to serialize the databse value to a string value which is then used to be sent
/// to the editor in JSON format.
/// A method used to format the databse value to a value that can be used by the editor
/// </summary>
/// <param name="dbValue"></param>
/// <returns></returns>
public virtual string FormatDataForEditor(object dbValue)
/// <remarks>
/// The object returned will automatically be serialized into json notation. For most property editors
/// the value returned is probably just a string but in some cases a json structure will be returned.
/// </remarks>
public virtual object FormatDataForEditor(object dbValue)
{
if (dbValue == null) return string.Empty;

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Umbraco.Core.Models;
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// A validator that validates that the value is of a certain type
/// </summary>
/// <remarks>
/// This is a special validator type that is executed against all property editors no matter if they've defined this validator or not.
/// </remarks>
[ValueValidator("ValueType")]
internal sealed class ValueTypeValueValidator : ValueValidator
{
public override IEnumerable<ValidationResult> Validate(string value, string config, string preValues, PropertyEditor editor)
{
var attempt = editor.ValueEditor.TryConvertValueToCrlType(value);
if (attempt.Success == false)
{
//TODO: localize these!
yield return new ValidationResult(string.Format("Value is not of type {0} and cannot be converted", editor.ValueEditor.GetDatabaseType()));
}
}
}
}

View File

@@ -24,7 +24,10 @@ namespace Umbraco.Core.PropertyEditors
/// <summary>
/// Performs the validation against the value
/// </summary>
/// <param name="value"></param>
/// <param name="value">
/// Depending on what is being validated, this value can be a json structure (JObject, JArray, etc...) representing an editor's model, it could be a single
/// string representing an editor's model.
/// </param>
/// <param name="config">
/// An object that is used to configure the validator. An example could be a regex
/// expression if the validator was a regex validator. This is defined in the manifest along with
@@ -37,6 +40,6 @@ namespace Umbraco.Core.PropertyEditors
/// the validation message applies to the entire property type being validated. If there is a field name applied to a
/// validation result we will try to match that field name up with a field name on the item itself.
/// </returns>
public abstract IEnumerable<ValidationResult> Validate(string value, string config, string preValues, PropertyEditor editor);
public abstract IEnumerable<ValidationResult> Validate(object value, string config, string preValues, PropertyEditor editor);
}
}

View File

@@ -585,7 +585,6 @@
<Compile Include="PropertyEditors\ValidatorBase.cs" />
<Compile Include="PropertyEditors\ValidatorsResolver.cs" />
<Compile Include="PropertyEditors\ValueEditor.cs" />
<Compile Include="PropertyEditors\ValueTypeValueValidator.cs" />
<Compile Include="PropertyEditors\ValueValidatorAttribute.cs" />
<Compile Include="PublishedContentExtensions.cs" />
<Compile Include="Dictionary\ICultureDictionary.cs" />

View File

@@ -13,31 +13,35 @@ namespace Umbraco.Web.UI.App_Plugins.MyPackage.PropertyEditors
internal class PostcodeValidator : ValidatorBase
{
public override IEnumerable<ValidationResult> Validate(string value, string preValues, PropertyEditor editor)
public override IEnumerable<ValidationResult> Validate(object value, string preValues, PropertyEditor editor)
{
var stringVal = value;
if (preValues.IsNullOrWhiteSpace()) yield break;
var asJson = JObject.Parse(preValues);
if (asJson["country"] == null) yield break;
if (asJson["country"].ToString() == "Australia")
if (value != null)
{
if (Regex.IsMatch(stringVal, "^\\d{4}$") == false)
var stringVal = value.ToString();
if (preValues.IsNullOrWhiteSpace()) yield break;
var asJson = JObject.Parse(preValues);
if (asJson["country"] == null) yield break;
if (asJson["country"].ToString() == "Australia")
{
yield return new ValidationResult("Australian postcodes must be a 4 digit number",
new[]
if (Regex.IsMatch(stringVal, "^\\d{4}$") == false)
{
yield return new ValidationResult("Australian postcodes must be a 4 digit number",
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"
});
}
}
else
{
yield return new ValidationResult("Only Australian postcodes are supported for this validator");
}
}
else
{
yield return new ValidationResult("Only Australian postcodes are supported for this validator");
}
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Web.Models.ContentEditing
public int Id { get; set; }
[DataMember(Name = "value")]
public string Value { get; set; }
public object Value { get; set; }
[DataMember(Name = "alias", IsRequired = true)]
[Required(AllowEmptyStrings = false)]

View File

@@ -40,14 +40,14 @@ namespace Umbraco.Web.PropertyEditors
/// CUstom value editor so we can serialize with the correct date format (excluding time)
/// and includes the date validator
/// </summary>
private class DateValueEditor : ValueEditorWrapper
internal class DateValueEditor : ValueEditorWrapper
{
public DateValueEditor(ValueEditor wrapped) : base(wrapped)
{
Validators = new List<ValidatorBase> { new DateTimeValidator() };
}
public override string FormatDataForEditor(object dbValue)
public override object FormatDataForEditor(object dbValue)
{
var date = dbValue.TryConvertTo<DateTime?>();
if (date.Success == false || date.Result == null)

View File

@@ -11,10 +11,10 @@ namespace Umbraco.Web.PropertyEditors
/// </summary>
internal class DateTimeValidator : ValidatorBase
{
public override IEnumerable<ValidationResult> Validate(string value, string preValues, PropertyEditor editor)
public override IEnumerable<ValidationResult> Validate(object value, string preValues, PropertyEditor editor)
{
DateTime dt;
if (value.IsNullOrWhiteSpace() == false && DateTime.TryParse(value, out dt) == false)
if (value != null && DateTime.TryParse(value.ToString(), out dt) == false)
{
yield return new ValidationResult(string.Format("The string value {0} cannot be parsed into a DateTime", value),
new[]

View File

@@ -1,43 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// A pre-value editor for the 'drop down list multiple' property editor that ensures that 'multiple' is saved for the config in the db but is not
/// rendered as a pre-value field.
/// </summary>
/// <remarks>
/// This is mostly to maintain backwards compatibility with old property editors. Devs can now simply use the Drop down property editor and check the multiple pre-value checkbox
/// </remarks>
internal class DropDownMultiplePreValueEditor : DropDownPreValueEditor
{
public DropDownMultiplePreValueEditor()
{
var fields = CreatePreValueFields();
//add the multiple field, we'll make it hidden so it is not seen in the pre-value editor
fields.Add(new PreValueField
{
Key = "multiple",
Name = "multiple",
View = "hidden"
});
Fields = fields;
}
/// <summary>
/// Always
/// </summary>
/// <param name="defaultPreVals"></param>
/// <param name="persistedPreVals"></param>
/// <returns></returns>
public override IDictionary<string, object> FormatDataForEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
{
var returnVal = base.FormatDataForEditor(defaultPreVals, persistedPreVals);
//always add the multiple param to true
returnVal["multiple"] = "1";
return returnVal;
}
}
}

View File

@@ -1,4 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
@@ -23,17 +29,88 @@ namespace Umbraco.Web.PropertyEditors
return new DropDownMultiplePreValueEditor();
}
}
internal class DropDownMultipleValueEditor : ValueEditorWrapper
{
public DropDownMultipleValueEditor(ValueEditor wrapped) : base(wrapped)
/// <summary>
/// A pre-value editor for the 'drop down list multiple' property editor that ensures that 'multiple' is saved for the config in the db but is not
/// rendered as a pre-value field.
/// </summary>
/// <remarks>
/// This is mostly to maintain backwards compatibility with old property editors. Devs can now simply use the Drop down property editor and check the multiple pre-value checkbox
/// </remarks>
internal class DropDownMultiplePreValueEditor : DropDownPreValueEditor
{
public DropDownMultiplePreValueEditor()
{
var fields = CreatePreValueFields();
//add the multiple field, we'll make it hidden so it is not seen in the pre-value editor
fields.Add(new PreValueField
{
Key = "multiple",
Name = "multiple",
View = "hidden"
});
Fields = fields;
}
/// <summary>
/// Always
/// </summary>
/// <param name="defaultPreVals"></param>
/// <param name="persistedPreVals"></param>
/// <returns></returns>
public override IDictionary<string, object> FormatDataForEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
{
var returnVal = base.FormatDataForEditor(defaultPreVals, persistedPreVals);
//always add the multiple param to true
returnVal["multiple"] = "1";
return returnVal;
}
}
public override object FormatDataForPersistence(Core.Models.Editors.ContentPropertyData editorValue, object currentValue)
/// <summary>
/// Custom value editor to handle posted json data and to return json data for the multiple selected items.
/// </summary>
internal class DropDownMultipleValueEditor : ValueEditorWrapper
{
return base.FormatDataForPersistence(editorValue, currentValue);
public DropDownMultipleValueEditor(ValueEditor wrapped)
: base(wrapped)
{
}
/// <summary>
/// Override so that we can return a json array to the editor for multi-select values
/// </summary>
/// <param name="dbValue"></param>
/// <returns></returns>
public override object FormatDataForEditor(object dbValue)
{
var delimited = base.FormatDataForEditor(dbValue).ToString();
return delimited.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// When multiple values are selected a json array will be posted back so we need to format for storage in
/// the database which is a comma separated ID value
/// </summary>
/// <param name="editorValue"></param>
/// <param name="currentValue"></param>
/// <returns></returns>
public override object FormatDataForPersistence(Core.Models.Editors.ContentPropertyData editorValue, object currentValue)
{
var json = editorValue.Value as JArray;
if (json == null)
{
return null;
}
var values = json.Select(item => item.Value<string>()).ToList();
//change to delimited
return string.Join(",", values);
}
}
}
}

View File

@@ -4,7 +4,9 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using umbraco;
namespace Umbraco.Web.PropertyEditors
@@ -30,27 +32,53 @@ namespace Umbraco.Web.PropertyEditors
return new DropDownValueEditor(base.CreateValueEditor());
}
}
/// <summary>
/// A property editor to allow the individual selection of pre-defined items.
/// </summary>
/// <remarks>
/// Due to remaining backwards compatible, this stores the id of the drop down item in the database which is why it is marked
/// as INT and we have logic in here to ensure it is formatted correctly including ensuring that the INT ID value is published
/// in cache and not the string value.
/// </remarks>
[PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeys, "Dropdown list, publishing keys", "dropdown", ValueType = "INT")]
public class DropDownWithKeysPropertyEditor : PropertyEditor
{
/// <summary>
/// Return a custom pre-value editor
/// A custom value editor for the drop down so that we can ensure that the 'value' not the ID get's put into cache
/// </summary>
/// <returns></returns>
protected override PreValueEditor CreatePreValueEditor()
/// <remarks>
/// This is required for legacy/backwards compatibility, otherwise we'd just store the string version and cache the string version without
/// needing additional lookups.
/// </remarks>
internal class DropDownValueEditor : ValueEditorWrapper
{
return new DropDownPreValueEditor();
private readonly DataTypeService _dataTypeService;
internal DropDownValueEditor(IDataTypeService dataTypeService, ValueEditor wrapped)
: base(wrapped)
{
_dataTypeService = (DataTypeService)dataTypeService;
}
public DropDownValueEditor(ValueEditor wrapped)
: this(ApplicationContext.Current.Services.DataTypeService, wrapped)
{
}
/// <summary>
/// Need to lookup the pre-values and put the string version in cache, not the ID (which is what is stored in the db)
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
public override object FormatValueForCache(Property property)
{
var preValId = property.Value.TryConvertTo<int>();
if (preValId.Success)
{
var preVals = _dataTypeService.GetPreValuesCollectionByDataTypeId(property.PropertyType.DataTypeDefinitionId);
if (preVals != null)
{
var dictionary = PreValueCollection.AsDictionary(preVals);
if (dictionary.Any(x => x.Value.Id == preValId.Result))
{
return dictionary.Single(x => x.Value.Id == preValId.Result).Value.Value;
}
LogHelper.Warn<DropDownValueEditor>("Could not find a pre value with ID " + preValId + " for property alias " + property.Alias);
}
}
return base.FormatValueForCache(property);
}
}
}
}

View File

@@ -1,59 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using System.Linq;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// A custom value editor for the drop down so that we can ensure that the 'value' not the ID get's put into cache
/// </summary>
/// <remarks>
/// This is required for legacy/backwards compatibility, otherwise we'd just store the string version and cache the string version without
/// needing additional lookups.
/// </remarks>
internal class DropDownValueEditor : ValueEditorWrapper
{
private readonly DataTypeService _dataTypeService;
internal DropDownValueEditor(IDataTypeService dataTypeService, ValueEditor wrapped)
: base(wrapped)
{
_dataTypeService = (DataTypeService)dataTypeService;
}
public DropDownValueEditor(ValueEditor wrapped)
: this(ApplicationContext.Current.Services.DataTypeService, wrapped)
{
}
/// <summary>
/// Need to lookup the pre-values and put the string version in cache, not the ID (which is what is stored in the db)
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
public override object FormatValueForCache(Property property)
{
var preValId = property.Value.TryConvertTo<int>();
if (preValId.Success)
{
var preVals = _dataTypeService.GetPreValuesCollectionByDataTypeId(property.PropertyType.DataTypeDefinitionId);
if (preVals != null)
{
var dictionary = PreValueCollection.AsDictionary(preVals);
if (dictionary.Any(x => x.Value.Id == preValId.Result))
{
return dictionary.Single(x => x.Value.Id == preValId.Result).Value.Value;
}
LogHelper.Warn<DropDownValueEditor>("Could not find a pre value with ID " + preValId + " for property alias " + property.Alias);
}
}
return base.FormatValueForCache(property);
}
}
}

View File

@@ -54,17 +54,10 @@ namespace Umbraco.Web.PropertyEditors
//check the editorValue value to see if we need to clear the files or not.
var clear = false;
if (editorValue.Value.IsNullOrWhiteSpace() == false && editorValue.Value.StartsWith("{clearFiles:"))
var json = editorValue.Value as JObject;
if (json != null && json["clearFiles"] != null && json["clearFiles"].Value<bool>())
{
try
{
var parsed = JObject.Parse(editorValue.Value);
clear = parsed["clearFiles"].Value<bool>();
}
catch (JsonReaderException)
{
clear = false;
}
clear = json["clearFiles"].Value<bool>();
}
var currentPersistedValues = new string[] {};

View File

@@ -318,11 +318,10 @@
<Compile Include="PropertyEditors\DatePropertyEditor.cs" />
<Compile Include="PropertyEditors\DateTimePropertyEditor.cs" />
<Compile Include="PropertyEditors\DateTimeValidator.cs" />
<Compile Include="PropertyEditors\DropDownMultiplePreValueEditor.cs" />
<Compile Include="PropertyEditors\DropDownMultiplePropertyEditor.cs" />
<Compile Include="PropertyEditors\DropDownPreValueEditor.cs" />
<Compile Include="PropertyEditors\DropDownPropertyEditor.cs" />
<Compile Include="PropertyEditors\DropDownValueEditor.cs" />
<Compile Include="PropertyEditors\DropDownWithKeysPropertyEditor.cs" />
<Compile Include="PropertyEditors\LabelPropertyEditor.cs" />
<Compile Include="PropertyEditors\LabelValueEditor.cs" />
<Compile Include="PropertyEditors\ValueEditorWrapper.cs" />