From 5911243908eba96aacffc8210bec4c5a180a6d51 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 30 Sep 2020 19:57:39 +0200 Subject: [PATCH] Add color picker component (#8812) --- .../components/umbcolorpicker.directive.js | 238 ++++++++++++++++++ .../components/umbrangeslider.directive.js | 2 +- .../components/umbtable.directive.js | 6 +- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-color-picker.less | 21 ++ .../src/less/property-editors.less | 15 -- .../colorpicker/colorpicker.prevalues.html | 20 +- .../multicolorpicker.controller.js | 90 +++---- 8 files changed, 318 insertions(+), 75 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-color-picker.less diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js new file mode 100644 index 0000000000..5e51a62d3e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcolorpicker.directive.js @@ -0,0 +1,238 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbColorPicker +@restrict E +@scope + +@description +Added in Umbraco v. 8.8: Use this directive to render a color picker. + +

Markup example

+
+    
+ + + + +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+    
+        function Controller() {
+    
+            var vm = this;
+
+            vm.options = {
+                type: "color",
+                color: defaultColor,
+                showAlpha: false,
+                showPalette: true,
+                showPaletteOnly: false,
+                preferredFormat: "hex",
+            };
+            
+            vm.show = show;
+            vm.hide = hide;
+            vm.change = change;
+
+            function show(color) {
+                color.toHexString().trimStart("#");
+            }
+
+            function hide(color) {
+                color.toHexString().trimStart("#");
+            }
+
+            function change(color) {
+                color.toHexString().trimStart("#");
+            }
+        }
+    
+        angular.module("umbraco").controller("My.ColorController", Controller);
+    
+    })();
+
+ +@param {string} ngModel (binding): Value for the color picker. +@param {object} options (binding): Config object for the color picker. +@param {function} onBeforeShow (expression): Callback function before color picker is shown. +@param {function} onChange (expression): Callback function when the color is changed. +@param {function} onShow (expression): Callback function when color picker is shown. +@param {function} onHide (expression): Callback function when color picker is hidden. +@param {function} onMove (expression): Callback function when the color is moved in color picker. + +**/ + +(function () { + 'use strict'; + + function ColorPickerController($scope, $element, $timeout, assetsService, localizationService) { + + const ctrl = this; + + let colorPickerInstance = null; + let labels = {}; + + ctrl.$onInit = function() { + + // load the separate css for the editor to avoid it blocking our js loading + assetsService.loadCss("lib/spectrum/spectrum.css", $scope); + + // load the js file for the color picker + assetsService.load([ + //"lib/spectrum/tinycolor.js", + "lib/spectrum/spectrum.js" + ], $scope).then(function () { + + // init color picker + grabElementAndRun(); + }); + + } + + function grabElementAndRun() { + + var labelKeys = [ + "general_cancel", + "general_choose", + "general_clear" + ]; + + localizationService.localizeMany(labelKeys).then(values => { + labels.cancel = values[0]; + labels.choose = values[1]; + labels.clear = values[2]; + }); + + $timeout(function () { + const element = $element.find('.umb-color-picker > input')[0]; + setColorPicker(element, labels); + }, 0, true); + + } + + function setColorPicker(element, labels) { + + // Spectrum options: https://seballot.github.io/spectrum/#options + + const defaultOptions = { + type: "color", + color: null, + showAlpha: false, + showInitial: false, + showInput: true, + cancelText: labels.cancel, + clearText: labels.clear, + chooseText: labels.choose, + preferredFormat: "hex", + clickoutFiresChange: true + }; + + // If has ngModel set the color + if (ctrl.ngModel) { + defaultOptions.color = ctrl.ngModel; + } + + //const options = ctrl.options ? ctrl.options : defaultOptions; + const options = Utilities.extend(defaultOptions, ctrl.options); + + var elem = angular.element(element); + + // Create new color pickr instance + const colorPicker = elem.spectrum(options); + + colorPickerInstance = colorPicker; + + if (colorPickerInstance) { + // destroy the color picker instance when the dom element is removed + elem.on('$destroy', function () { + colorPickerInstance.spectrum('destroy'); + }); + } + + setUpCallbacks(); + + // Refresh the scope + $scope.$applyAsync(); + } + + // Spectrum events: https://seballot.github.io/spectrum/#events + + function setUpCallbacks() { + + if (colorPickerInstance) { + + // bind hook for beforeShow + if (ctrl.onBeforeShow) { + colorPickerInstance.on('beforeShow.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onBeforeShow({ color: tinycolor }); + }); + }); + } + + // bind hook for show + if (ctrl.onShow) { + colorPickerInstance.on('show.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onShow({ color: tinycolor }); + }); + }); + } + + // bind hook for hide + if (ctrl.onHide) { + colorPickerInstance.on('hide.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onHide({ color: tinycolor }); + }); + }); + } + + // bind hook for change + if (ctrl.onChange) { + colorPickerInstance.on('change.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onChange({ color: tinycolor }); + }); + }); + } + + // bind hook for move + if (ctrl.onMove) { + colorPickerInstance.on('move.spectrum', (e, tinycolor) => { + $timeout(function () { + ctrl.onMove({ color: tinycolor }); + }); + }); + } + + } + } + } + + angular + .module('umbraco.directives') + .component('umbColorPicker', { + template: '
', + controller: ColorPickerController, + bindings: { + ngModel: '<', + options: '<', + onBeforeShow: '&', + onShow: '&', + onHide: '&', + onChange: '&', + onMove: '&' + } + }); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js index e467522c84..ed74f94f26 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js @@ -46,7 +46,7 @@ For extra details about options and events take a look here: https://refreshless @param {object} ngModel (binding): Value for the slider. -@param {object} options (binding): Config object for the date picker. +@param {object} options (binding): Config object for the slider. @param {callback} onSetup (callback): onSetup gets triggered when the slider is initialized @param {callback} onUpdate (callback): onUpdate fires every time the slider values are changed. @param {callback} onSlide (callback): onSlide gets triggered when the handle is being dragged. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index 3f1929e97d..1554c136b6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -98,9 +98,9 @@ })(); -@param {string} icon (binding): The node icon. -@param {string} name (binding): The node name. -@param {string} published (binding): The node published state. +@param {array} items (binding): The items for the table. +@param {array} itemProperties (binding): The properties for the items to use in table. +@param {boolean} allowSelectAll (binding): Specify whether to allow select all. @param {function} onSelect (expression): Callback function when the row is selected. @param {function} onClick (expression): Callback function when the "Name" column link is clicked. @param {function} onSelectAll (expression): Callback function when selecting all items. diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index e5ce8d572c..01e595ac24 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -143,6 +143,7 @@ @import "components/umb-property-editor.less"; @import "components/umb-property-actions.less"; @import "components/umb-code-snippet.less"; +@import "components/umb-color-picker.less"; @import "components/umb-color-swatches.less"; @import "components/check-circle.less"; @import "components/umb-file-icon.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-picker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-picker.less new file mode 100644 index 0000000000..21439efeca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-picker.less @@ -0,0 +1,21 @@ +.umb-color-picker { + + .sp-replacer { + display: inline-flex; + margin-right: 18px; + height: 32px; + + &.sp-light { + background-color: @white; + } + + .sp-preview { + margin: 5px; + height: auto; + } + + .sp-dd { + line-height: 2rem; + } + } +} 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 3912d11161..ce62f7c1cd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -160,21 +160,6 @@ width: auto; } - .sp-replacer { - display: inline-flex; - margin-right: 18px; - height: auto; - - .sp-preview { - margin: 5px; - height: auto; - } - - .sp-dd { - line-height: 2rem; - } - } - label { border: 1px solid @white; padding: 6px 10px; 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 dd572a83bf..92d29cbce8 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,20 @@ -
+
- - - + + + + + +
- +
@@ -21,7 +29,7 @@
- +
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 79d21f8e25..bfbe2f6d9d 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 @@ -1,5 +1,17 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.MultiColorPickerController", - function ($scope, $timeout, assetsService, angularHelper, $element, localizationService, eventsService) { + function ($scope, angularHelper, $element, eventsService) { + + var vm = this; + + vm.add = add; + vm.remove = remove; + + vm.show = show; + vm.hide = hide; + vm.change = change; + + vm.labelEnabled = false; + //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; @@ -9,52 +21,33 @@ $scope.hasError = false; $scope.focusOnNew = false; - $scope.labels = {}; + $scope.options = { + type: "color", + color: defaultColor, + allowEmpty: false, + showAlpha: false + }; - var labelKeys = [ - "general_cancel", - "general_choose" - ]; + function hide(color) { + // show the add button + $element.find(".btn.add").show(); + } - $scope.labelEnabled = false; - eventsService.on("toggleValue", function (e, args) { - $scope.labelEnabled = args.value; - }); - - localizationService.localizeMany(labelKeys).then(function (values) { - $scope.labels.cancel = values[0]; - $scope.labels.choose = values[1]; - }); + function show(color) { + // hide the add button + $element.find(".btn.add").hide(); + } - assetsService.load([ - //"lib/spectrum/tinycolor.js", - "lib/spectrum/spectrum.js" - ], $scope).then(function () { - var elem = $element.find("input[name='newColor']"); - elem.spectrum({ - type: "color", - color: defaultColor, - showAlpha: false, - showInitial: false, - showInput: true, - chooseText: $scope.labels.choose, - cancelText: $scope.labels.cancel, - preferredFormat: "hex", - clickoutFiresChange: true, - hide: function (color) { - //show the add butotn - $element.find(".btn.add").show(); - }, - change: function (color) { - angularHelper.safeApply($scope, function () { - $scope.newColor = color.toHexString().trimStart("#"); // #ff0000 - }); - }, - show: function() { - //hide the add butotn - $element.find(".btn.add").hide(); + function change(color) { + angularHelper.safeApply($scope, function () { + if (color) { + $scope.newColor = color.toHexString().trimStart("#"); } }); + } + + eventsService.on("toggleValue", function (e, args) { + vm.labelEnabled = args.value; }); if (!Utilities.isArray($scope.model.value)) { @@ -96,7 +89,7 @@ return label !== null && typeof label !== "undefined" && label !== "" && label.length && label.length > 0; } - $scope.remove = function (item, evt) { + function remove(item, evt) { evt.preventDefault(); @@ -105,9 +98,9 @@ }); angularHelper.getCurrentForm($scope).$setDirty(); - }; + } - $scope.add = function (evt) { + function add(evt) { evt.preventDefault(); if ($scope.newColor) { @@ -127,11 +120,10 @@ return; } - //there was an error, do the highlight (will be set back by the directive) + // there was an error, do the highlight (will be set back by the directive) $scope.hasError = true; } - - }; + } $scope.sortableOptions = { axis: 'y', @@ -145,6 +137,4 @@ } }; - //load the separate css for the editor to avoid it blocking our js loading - assetsService.loadCss("lib/spectrum/spectrum.css", $scope); });