Skip to main content
Error Showing Content - please login as admin for details.
Error Showing Content - please login as admin for details.

Customize Edit UI/UX

Tutorial Home

2sxc Custom Color-Picker Input Field - Configurable

Input fields may expect some configuration - like default colors or WebApi endpoints. To enable such a configuration, we need a Content-Type which defines all the fields that can be configured. This can be in the app-data, or it can be stored in a subfolder of the field-extension, to make redestribution easier.
This example contains the json-exported content-type in the folder /system/field-string-app-color-pickr-pro/.data/contenttypes/ so you could just copy the extension folder to another app and use it from there.

So in this tutorial you'll learn how to:

  • To see how the UI changes based on field configuration
  • How to access such pre-configured settings with connector.field.settings
  • The Content-Type for configuration is included in the extension folder

Important: The feature "Public Use of Edit Form" is disabled

The feature is needed for anonymous users to use the Edit Form. Register your site here https://patrons.2sxc.org/ to get access to the feature.

New syntax: Configurable Color-Picker Input Field

This example shows two color-picker fields, with different initial configurations.
Hit this edit button to have a look:


1
2
@Kit.Toolbar.Empty().New("UiStringColorPickrPro").AsTag()
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Old syntax: Configurable Color-Picker Input Field

This example shows two color-picker fields, with different initial configurations.
Hit this edit button to have a look:


1
2
@Edit.Toolbar(toolbar: new [] { "toolbar=empty", "+new?contentType=UiStringColorPickrPro" })
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Important: We opened permissions that you can experience the edit dialog - so you can save, but it will just create draft data 😉.

Source of [App]/system/field-string-app-color-pickr/index.js

Below you'll see the source code of the file. Note that we're just showing the main part, and hiding some parts of the file which are not relevant for understanding the essentials. Click to expand the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
This examples shows a plain JS WebComponent which has has a Pickr color-picker
Uses the neat Pickr from https://simonwep.github.io/pickr/
This pro-picker allows the admin to define a set of recommended colors
*/
// always use an IFFE to ensure you don't put variables in the window scope
(() => {
const tagName = 'field-string-app-color-pickr-pro';
const pickrJsCdn = 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js';
const html = `
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css"/>
<div class="pickr-container"></div>`;
class StringColorPickerFlat extends HTMLElement {
/* Constructor for WebComponents - the first line must always be super() */
constructor() {
super();
}
/* connectedCallback() is the standard callback when the component has been attached */
connectedCallback() {
this.innerHTML = html;
// if the window.Pickr doesn't exist yet, load the JS from the CDN () and then do a callback
this.connector.loadScript('Pickr', pickrJsCdn, () => { this.initPick() });
}
/** disconnectedCallback() is a standard callback for clean-up */
disconnectedCallback() {
if (this.pickr) this.pickr.destroyAndRemove();
}
/** This is called when the JS is loaded from loadScript - so Pickr is ready */
initPick() {
this.pickr = new Pickr({
el: '.pickr-container',
theme: 'classic',
default: this.connector.data.value || null,
defaultRepresentation: 'HEXA',
swatches: this.getSwatches(),
components: {
// Main components
preview: true,
opacity: true,
hue: true,
// Input / output Options
interaction: {
hex: true,
rgba: true,
hsla: true,
hsva: true,
cmyk: true,
input: true,
cancel: true,
clear: true,
save: true,
},
},
});
// remember if we're working empty as of now
this.cleared = !this.connector.data.value;
// bind events for changes etc. to live-update the preview
this.pickr.on('change', (color, instance) => this.applyColor(instance));
this.pickr.on('changestop', (instance) => this.applyColor(instance));
this.pickr.on('swatchselect', (color, instance) => this.applyColor(instance));
this.pickr.on('save', (color,instance) => instance.hide());
this.pickr.on('hide', (instance) => this.update(instance));
this.pickr.on('clear', (instance) => {
this.cleared = true;
this.update();
});
}
/** Update the preview */
applyColor(instance) {
this.cleared = false;
instance.applyColor(true);
}
/** Update the value */
update(instance) {
// if it's still cleared, just save null
if (this.cleared) {
return this.updateIfChanged(null);
}
// otherwise get the current color
var color = instance.getColor();
if (color) color = color.toHEXA().toString();
this.updateIfChanged(color);
}
/** Only update the value if it really changed, so form isn't dirty if nothing was set */
updateIfChanged(value) {
var data = this.connector.data;
if (data.value === '' && value == null) return;
if (data.value === value) return;
data.update(value);
}
/** Load the settings and convert to swatch-list */
getSwatches() {
// the field "Swatches" is the field in the content-type containing the colors
// it's upper-case, because that's how the field is named
var swatches = this.connector.field.settings.Swatches;
if (!swatches) return [];
return swatches.split('\n').map((colorLine) => {
var withLabel = colorLine.trim().split(' ');
return withLabel[0]; // first part is the color
});
}
}
// Register this web component - if it hasn't been registered yet
if (!customElements.get(tagName)) customElements.define(tagName, StringColorPickerFlat);
})();
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Source of [App]/system/field-string-app-color-pickr-pro/.data/contenttypes/System.Fields.459d4ce3-b96b-4664-9947-75e46df9d33e.json

Below you'll see the source code of the file. Note that we're just showing the main part, and hiding some parts of the file which are not relevant for understanding the essentials. Click to expand the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
{
"_": { "V": 1 },
"ContentType": {
"Id": "459d4ce3-b96b-4664-9947-75e46df9d33e",
"Name": "@string-app-color-pickr-pro",
"Scope": "System.Fields",
"Description": "",
"Attributes": [
{
"Name": "Swatches",
"Type": "String",
"InputType": "string-default",
"IsTitle": true,
"Metadata": [
{
"Id": 74876,
"Version": 2,
"Guid": "47026444-2298-4f78-8210-d088ec1ea14c",
"Type": { "Name": "@All", "Id": "@All" },
"Attributes": {
"String": {
"CustomJavaScript": { "*": "" },
"DefaultValue": { "*": "" },
"InputType": { "*": "string-default" },
"Name": { "*": "Swatches" },
"Notes": {
"*": "<p>Place recommended color codes - one on each line. Use HEXA format, like #770088aa
                      </p>"
},
"ValidationRegExJavaScript": { "*": "" }
},
"Entity": { "Errors": { "*": [] }, "Warnings": { "*": [] } },
"Boolean": {
"Disabled": { "*": false },
"Required": { "*": false },
"VisibleInEditUI": { "*": true }
}
},
"Owner": "dnn:userid=1"
},
{
"Id": 74877,
"Version": 1,
"Guid": "75e73a53-b6cc-4f65-9cf8-22cdcb8fbc7f",
"Type": { "Name": "@String", "Id": "@String" },
"Attributes": {
"String": {
"DropdownValues": { "*": "" },
"InputType": { "*": "" }
}
},
"Owner": "dnn:userid=1"
},
{
"Id": 74878,
"Version": 1,
"Guid": "dea3bf2f-3a95-4a05-adcf-353de7ec07c0",
"Type": { "Name": "@string-default", "Id": "@string-default" },
"Attributes": { "Number": { "RowCount": { "*": 10.0 } } },
"Owner": "dnn:userid=1"
}
]
}
],
"Metadata": []
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX