diff --git a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs index d773eed2e9..0c8bec8f2e 100644 --- a/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PreValueEditor.cs @@ -178,29 +178,25 @@ namespace Umbraco.Core.PropertyEditors return result; } - private void ConvertItemsToJsonIfDetected(IDictionary result) + protected void ConvertItemsToJsonIfDetected(IDictionary result) { - //now we're going to try to see if any of the values are JSON, if they are we'll convert them to real JSON objects - // so they can be consumed as real json in angular! + // convert values that are Json to true Json objects that can be consumed by Angular var keys = result.Keys.ToArray(); for (var i = 0; i < keys.Length; i++) { - if (result[keys[i]] is string) + if ((result[keys[i]] is string) == false) continue; + + var asString = result[keys[i]].ToString(); + if (asString.DetectIsJson() == false) continue; + + try { - var asString = result[keys[i]].ToString(); - if (asString.DetectIsJson()) - { - try - { - var json = JsonConvert.DeserializeObject(asString); - result[keys[i]] = json; - } - catch - { - //swallow this exception, we thought it was json but it really isn't so continue returning a string - } - } + result[keys[i]] = JsonConvert.DeserializeObject(asString); + } + catch + { + // swallow this exception, we thought it was Json but it really isn't so continue returning a string } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs index 987640716b..b0d2e0809d 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ColorPickerValueConverter.cs @@ -1,13 +1,14 @@ using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Configuration; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors.ValueConverters { [DefaultPropertyValueConverter] - [PropertyValueType(typeof(string))] - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] - public class ColorPickerValueConverter : PropertyValueConverterBase + public class ColorPickerValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta { public override bool IsConverter(PublishedPropertyType propertyType) { @@ -18,11 +19,60 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters return false; } + public Type GetPropertyValueType(PublishedPropertyType propertyType) + { + return UseLabel(propertyType) ? typeof(PickedColor) : typeof(string); + } + + public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) + { + return PropertyCacheLevel.Content; + } + + private bool UseLabel(PublishedPropertyType propertyType) + { + var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + PreValue preValue; + return preValues.PreValuesAsDictionary.TryGetValue("useLabel", out preValue) && preValue.Value == "1"; + } + public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) { - // make sure it's a string - return source == null ? string.Empty : source.ToString(); + var useLabel = UseLabel(propertyType); + + if (source == null) return useLabel ? null : string.Empty; + + var ssource = source.ToString(); + if (ssource.DetectIsJson()) + { + try + { + var jo = JsonConvert.DeserializeObject(ssource); + if (useLabel) return new PickedColor(jo["value"].ToString(), jo["label"].ToString()); + return jo["value"].ToString(); + } + catch { /* not json finally */ } + } + + if (useLabel) return new PickedColor(ssource, ssource); + return ssource; + } + + public class PickedColor + { + public PickedColor(string color, string label) + { + Color = color; + Label = label; + } + + public string Color { get; private set; } + public string Label { get; private set; } + + public override string ToString() + { + return Color; + } } - } } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 7fb3ac8f46..ffaa1a6a92 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -109,16 +109,48 @@ ul.color-picker li a { } /* pre-value editor */ - +/*.control-group.color-picker-preval:before { + content: ""; + display: inline-block; + vertical-align: middle; + height: 100%; +}*/ + +/*.control-group.color-picker-preval div.thumbnail { + display: inline-block; + vertical-align: middle; +}*/ +.control-group.color-picker-preval div.color-picker-prediv { + display: inline-block; + width: 60%; +} + .control-group.color-picker-preval pre { display: inline; margin-right: 20px; margin-left: 10px; + width: 50%; + white-space: nowrap; + overflow: hidden; + margin-bottom: 0; + vertical-align: middle; } +.control-group.color-picker-preval btn { + //vertical-align: middle; +} + +.control-group.color-picker-preval input[type="text"] { + min-width: 40%; + width: 40%; + display: inline-block; + margin-right: 20px; + margin-top: 1px; +} + .control-group.color-picker-preval label { - border:solid @white 1px; - padding:6px; + border: solid @white 1px; + padding: 6px; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js index f14492e88a..7d847dbc83 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js @@ -1,25 +1,131 @@ -function ColorPickerController($scope) { - $scope.toggleItem = function (color) { - if ($scope.model.value == color) { - $scope.model.value = ""; - //this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue($scope.model.value); - } - else { - $scope.model.value = color; - //this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue($scope.model.value); - } - }; +function ColorPickerController($scope) { + + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + if ($scope.isConfigured) { + + for (var key in $scope.model.config.items) { + if (!$scope.model.config.items[key].hasOwnProperty("value")) + $scope.model.config.items[key] = { value: $scope.model.config.items[key], label: $scope.model.config.items[key] }; + } + + $scope.model.useLabel = isTrue($scope.model.config.useLabel); + initActiveColor(); + } + + $scope.toggleItem = function (color) { + + var currentColor = $scope.model.value.hasOwnProperty("value") + ? $scope.model.value.value + : $scope.model.value; + + var newColor; + if (currentColor === color.value) { + // deselect + $scope.model.value = $scope.model.useLabel ? { value: "", label: "" } : ""; + newColor = ""; + } + else { + // select + $scope.model.value = $scope.model.useLabel ? { value: color.value, label: color.label } : color.value; + newColor = color.value; + } + + // this is required to re-validate + $scope.propertyForm.modelValue.$setViewValue(newColor); + }; + // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected) - $scope.validateMandatory = function () { + $scope.validateMandatory = function () { + var isValid = !$scope.model.validation.mandatory || ( + $scope.model.value != null + && $scope.model.value != "" + && (!$scope.model.value.hasOwnProperty("value") || $scope.model.value.value !== "") + ); return { - isValid: !$scope.model.validation.mandatory || ($scope.model.value != null && $scope.model.value != ""), + isValid: isValid, errorMsg: "Value cannot be empty", errorKey: "required" }; } - $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + // A color is active if it matches the value and label of the model. + // If the model doesn't store the label, ignore the label during the comparison. + $scope.isActiveColor = function (color) { + + // no value + if (!$scope.model.value) + return false; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return $scope.model.value === color.value; + + return $scope.model.value.value === color.value && $scope.model.value.label === color.label; + }; + + // Finds the color best matching the model's color, + // and sets the model color to that one. This is useful when + // either the value or label was changed on the data type. + function initActiveColor() { + + // no value + if (!$scope.model.value) + return; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return; + + var modelColor = $scope.model.value.value; + var modelLabel = $scope.model.value.label; + + // Check for a full match or partial match. + var foundItem = null; + + // Look for a fully matching color. + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor && item.label == modelLabel) { + foundItem = item; + break; + } + } + + // Look for a color with a matching value. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor) { + foundItem = item; + break; + } + } + } + + // Look for a color with a matching label. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.label == modelLabel) { + foundItem = item; + break; + } + } + } + + // If a match was found, set it as the active color. + if (foundItem) { + $scope.model.value.value = foundItem.value; + $scope.model.value.label = foundItem.label; + } + } + + // figures out if a value is trueish enough + function isTrue(bool) { + return !!bool && bool !== "0" && angular.lowercase(bool) !== "false"; + } } angular.module("umbraco").controller("Umbraco.PropertyEditors.ColorPickerController", ColorPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html index a493fffdd8..46b624adcc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.html @@ -5,10 +5,10 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index a772f830c5..2b917e69f8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -1,12 +1,13 @@
+ + -
-
{{item.value}}
+
#{{item.value}} - {{item.label}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js index d31ac911a9..dd25741aeb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/multicolorpicker.controller.js @@ -2,15 +2,17 @@ function ($scope, $timeout, assetsService, angularHelper, $element) { //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive. var defaultColor = "000000"; - + var defaultLabel = null; + $scope.newColor = defaultColor; + $scope.newLavel = defaultLabel; $scope.hasError = false; assetsService.load([ //"lib/spectrum/tinycolor.js", - "lib/spectrum/spectrum.js" + "lib/spectrum/spectrum.js" ], $scope).then(function () { - var elem = $element.find("input"); + var elem = $element.find("input[name='newColor']"); elem.spectrum({ color: null, showInitial: false, @@ -21,7 +23,7 @@ clickoutFiresChange: true, hide: function (color) { //show the add butotn - $element.find(".btn.add").show(); + $element.find(".btn.add").show(); }, change: function (color) { angularHelper.safeApply($scope, function () { @@ -39,21 +41,41 @@ //make an array from the dictionary var items = []; for (var i in $scope.model.value) { - items.push({ - value: $scope.model.value[i], - id: i - }); + var oldValue = $scope.model.value[i]; + if (oldValue.hasOwnProperty("value")) { + items.push({ + value: oldValue.value, + label: oldValue.label, + id: i + }); + } else { + items.push({ + value: oldValue, + label: oldValue, + id: i + }); + } } //now make the editor model the array $scope.model.value = items; } + // ensure labels + for (var i = 0; i < $scope.model.value.length; i++) { + var item = $scope.model.value[i]; + item.label = item.hasOwnProperty("label") ? item.label : item.value; + } + + function validLabel(label) { + return label !== null && typeof label !== "undefined" && label !== "" && label.length && label.length > 0; + } + $scope.remove = function (item, evt) { evt.preventDefault(); $scope.model.value = _.reject($scope.model.value, function (x) { - return x.value === item.value; + return x.value === item.value && x.label === item.label; }); }; @@ -63,15 +85,15 @@ evt.preventDefault(); if ($scope.newColor) { + var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor; var exists = _.find($scope.model.value, function(item) { - return item.value.toUpperCase() == $scope.newColor.toUpperCase(); + return item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase(); }); if (!exists) { - $scope.model.value.push({ value: $scope.newColor }); - //$scope.newColor = defaultColor; - // set colorpicker to default color - //var elem = $element.find("input"); - //elem.spectrum("set", $scope.newColor); + $scope.model.value.push({ + value: $scope.newColor, + label: newLabel + }); $scope.hasError = false; return; } diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs index e7750c4c65..a504ed0431 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs @@ -1,9 +1,12 @@ +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text.RegularExpressions; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -11,7 +14,7 @@ namespace Umbraco.Web.PropertyEditors { internal class ColorListPreValueEditor : ValueListPreValueEditor { - + public ColorListPreValueEditor() { var field = Fields.First(); @@ -23,14 +26,98 @@ namespace Umbraco.Web.PropertyEditors //change the label field.Name = "Add color"; //need to have some custom validation happening here - field.Validators.Add(new ColorListValidator()); + field.Validators.Add(new ColorListValidator()); + + Fields.Insert(0, new PreValueField + { + Name = "Include labels?", + View = "boolean", + Key = "useLabel", + Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string." + }); } public override IDictionary ConvertDbToEditor(IDictionary defaultPreVals, PreValueCollection persistedPreVals) { var dictionary = persistedPreVals.FormatAsDictionary(); - var arrayOfVals = dictionary.Select(item => item.Value).ToList(); - return new Dictionary { { "items", arrayOfVals.ToDictionary(x => x.Id, x => x.Value) } }; + var items = dictionary + .Where(x => x.Key != "useLabel") + .ToDictionary(x => x.Value.Id, x => x.Value.Value); + + var items2 = new Dictionary(); + foreach (var item in items) + { + if (item.Value.DetectIsJson() == false) + { + items2[item.Key] = item.Value; + continue; + } + + try + { + items2[item.Key] = JsonConvert.DeserializeObject(item.Value); + } + catch + { + // let's say parsing Json failed, so what we have is the string - build json + items2[item.Key] = new JObject { { "color", item.Value }, { "label", item.Value } }; + } + } + + var result = new Dictionary { { "items", items2 } }; + var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1"; + if (useLabel) + result["useLabel"] = dictionary["useLabel"].Value; + + return result; + } + + public override IDictionary ConvertEditorToDb(IDictionary editorValue, PreValueCollection currentValue) + { + var val = editorValue["items"] as JArray; + var result = new Dictionary(); + if (val == null) return result; + + try + { + object useLabelObj; + var useLabel = false; + if (editorValue.TryGetValue("useLabel", out useLabelObj)) + { + useLabel = useLabelObj is string && (string) useLabelObj == "1"; + result["useLabel"] = new PreValue(useLabel ? "1" : "0"); + } + + // get all non-empty values + var index = 0; + foreach (var preValue in val.OfType() + .Where(x => x["value"] != null) + .Select(x => + { + var idString = x["id"] == null ? "0" : x["id"].ToString(); + int id; + if (int.TryParse(idString, out id) == false) id = 0; + + var color = x["value"].ToString(); + if (string.IsNullOrWhiteSpace(color)) return null; + + var label = x["label"].ToString(); + return new PreValue(id, useLabel + ? JsonConvert.SerializeObject(new { value = color, label = label }) + : color); + }) + .WhereNotNull()) + { + result.Add(index.ToInvariantString(), preValue); + index++; + } + } + catch (Exception ex) + { + LogHelper.Error("Could not deserialize the posted value: " + val, ex); + } + + return result; } internal class ColorListValidator : IPropertyValidator @@ -39,7 +126,7 @@ namespace Umbraco.Web.PropertyEditors { var json = value as JArray; if (json == null) yield break; - + //validate each item which is a json object for (var index = 0; index < json.Count; index++) {