using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Collections; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Constants.PropertyEditors.MultipleTextstringAlias, "Repeatable textstrings", "multipletextbox", ValueType = PropertyEditorValueTypes.Text, Icon="icon-ordered-list", Group="lists")] public class MultipleTextStringPropertyEditor : PropertyEditor { protected override PropertyValueEditor CreateValueEditor() { return new MultipleTextStringPropertyValueEditor(base.CreateValueEditor()); } protected override PreValueEditor CreatePreValueEditor() { return new MultipleTextStringPreValueEditor(); } /// /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. /// internal class MultipleTextStringPreValueEditor : PreValueEditor { public MultipleTextStringPreValueEditor() { //create the fields Fields.Add(new PreValueField(new IntegerValidator()) { Description = "Enter the minimum amount of text boxes to be displayed", Key = "min", View = "requiredfield", Name = "Minimum" }); Fields.Add(new PreValueField(new IntegerValidator()) { Description = "Enter the maximum amount of text boxes to be displayed, enter 0 for unlimited", Key = "max", View = "requiredfield", Name = "Maximum" }); } /// /// Need to change how we persist the values so they are compatible with the legacy way we store values /// /// /// /// public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) { //the values from the editor will be min/max fieds and we need to format to json in one field var min = (editorValue.ContainsKey("min") ? editorValue["min"].ToString() : "0").TryConvertTo(); var max = (editorValue.ContainsKey("max") ? editorValue["max"].ToString() : "0").TryConvertTo(); var json = JObject.FromObject(new {Minimum = min.Success ? min.Result : 0, Maximum = max.Success ? max.Result : 0}); return new Dictionary { { "0", new PreValue(json.ToString(Formatting.None)) } }; } /// /// Need to deal with the legacy way of storing pre-values and turn them into nice values for the editor /// /// /// /// public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) { var preVals = persistedPreVals.FormatAsDictionary(); var stringVal = preVals.Any() ? preVals.First().Value.Value : ""; var returnVal = new Dictionary { { "min", 0 }, { "max", 0 } }; if (stringVal.IsNullOrWhiteSpace() == false) { try { var json = JsonConvert.DeserializeObject(stringVal); if (json["Minimum"] != null) { //by default pre-values are sent out with an id/value pair returnVal["min"] = json["Minimum"].Value(); } if (json["Maximum"] != null) { returnVal["max"] = json["Maximum"].Value(); } } catch (Exception e) { //this shouldn't happen unless there's already a bad formatted pre-value LogHelper.WarnWithException("Could not deserialize value to json " + stringVal, e); return returnVal; } } return returnVal; } } /// /// Custom value editor so we can format the value for the editor and the database /// internal class MultipleTextStringPropertyValueEditor : PropertyValueEditorWrapper { public MultipleTextStringPropertyValueEditor(PropertyValueEditor wrapped) : base(wrapped) { } /// /// The value passed in from the editor will be an array of simple objects so we'll need to parse them to get the string /// /// /// /// /// /// We will also check the pre-values here, if there are more items than what is allowed we'll just trim the end /// public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue) { var asArray = editorValue.Value as JArray; if (asArray == null) { return null; } var preVals = editorValue.PreValues.FormatAsDictionary(); var max = -1; if (preVals.Any()) { try { var json = JsonConvert.DeserializeObject(preVals.First().Value.Value); max = int.Parse(json["Maximum"].ToString()); } catch (Exception) { //swallow max = -1; } } //The legacy property editor saved this data as new line delimited! strange but we have to maintain that. var array = asArray.OfType() .Where(x => x["value"] != null) .Select(x => x["value"].Value()); //only allow the max if over 0 if (max > 0) { return string.Join(Environment.NewLine, array.Take(max)); } return string.Join(Environment.NewLine, array); } /// /// We are actually passing back an array of simple objects instead of an array of strings because in angular a primitive (string) value /// cannot have 2 way binding, so to get around that each item in the array needs to be an object with a string. /// /// /// /// /// /// /// The legacy property editor saved this data as new line delimited! strange but we have to maintain that. /// public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService) { return property.Value == null ? new JObject[] {} : property.Value.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) .Select(x => JObject.FromObject(new {value = x})); } } } }