U4-5322 - add labels to color picker
This commit is contained in:
@@ -178,29 +178,25 @@ namespace Umbraco.Core.PropertyEditors
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ConvertItemsToJsonIfDetected(IDictionary<string, object> result)
|
||||
protected void ConvertItemsToJsonIfDetected(IDictionary<string, object> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<JObject>(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
</div>
|
||||
|
||||
<ul class="thumbnails color-picker">
|
||||
<li ng-repeat="(key, val) in model.config.items" ng-class="{active: model.value === val}">
|
||||
<a ng-click="toggleItem(val)" class="thumbnail" hex-bg-color="{{val}}">
|
||||
|
||||
</a>
|
||||
<li ng-repeat="(key, val) in model.config.items" ng-class="{active: isActiveColor(val)}">
|
||||
<a ng-click="toggleItem(val)" class="thumbnail" hex-bg-color="{{val.value}}">
|
||||
</a>
|
||||
<span class="color-label" ng-if="model.useLabel" ng-bind="val.label"></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<div ng-controller="Umbraco.PrevalueEditors.MultiColorPickerController">
|
||||
<div class="control-group color-picker-preval">
|
||||
<input name="newColor" type="hidden" />
|
||||
<label for="newColor" val-highlight="hasError">#{{newColor}}</label>
|
||||
<input name="newLabel" type="text" ng-model="newLabel" class="umb-editor color-label" placeholder="Label" />
|
||||
<button class="btn add" ng-click="add($event)"><localize key="general_add">Add</localize></button>
|
||||
<label for="newColor" val-highlight="hasError">{{newColor}}</label>
|
||||
</div>
|
||||
<div class="control-group color-picker-preval" ng-repeat="item in model.value">
|
||||
<div class="thumbnail span1" hex-bg-color="{{item.value}}" bg-orig="transparent"></div>
|
||||
<pre>{{item.value}}</pre>
|
||||
<div class="color-picker-prediv"><pre>#{{item.value}} - {{item.label}}</pre></div>
|
||||
<button class="btn btn-danger" ng-click="remove(item, $event)"><localize key="general_remove">Remove</localize></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<string, object> ConvertDbToEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
|
||||
{
|
||||
var dictionary = persistedPreVals.FormatAsDictionary();
|
||||
var arrayOfVals = dictionary.Select(item => item.Value).ToList();
|
||||
return new Dictionary<string, object> { { "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<int, object>();
|
||||
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<string, object> { { "items", items2 } };
|
||||
var useLabel = dictionary.ContainsKey("useLabel") && dictionary["useLabel"].Value == "1";
|
||||
if (useLabel)
|
||||
result["useLabel"] = dictionary["useLabel"].Value;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override IDictionary<string, PreValue> ConvertEditorToDb(IDictionary<string, object> editorValue, PreValueCollection currentValue)
|
||||
{
|
||||
var val = editorValue["items"] as JArray;
|
||||
var result = new Dictionary<string, PreValue>();
|
||||
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<JObject>()
|
||||
.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<ValueListPreValueEditor>("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++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user