Implement color picker. Expose `VanillaPicker`, `ace`, and `Ajv`.

This commit is contained in:
jos 2018-08-22 12:10:15 +02:00
parent 4f72c5e113
commit b853ec3f64
17 changed files with 229 additions and 41 deletions

View File

@ -3,6 +3,12 @@
https://github.com/josdejong/jsoneditor https://github.com/josdejong/jsoneditor
## not yet released, version 5.24.0
- Implemented a color picker, and allow hooking in a custom color
picker. new options are `colorPicker` and `onColorPicker`.
## 2018-08-17, version 5.23.1 ## 2018-08-17, version 5.23.1
- Fixed #566: transform function broken, regression since `v5.20.0`. - Fixed #566: transform function broken, regression since `v5.20.0`.

View File

@ -24,10 +24,11 @@ Cross browser testing for JSONEditor is generously provided by <a href="https://
## Features ## Features
### Tree editor ### Tree editor
- Edit, add, move, remove, and duplicate fields and values. - Change, add, move, remove, and duplicate fields and values.
- Change type of values.
- Sort arrays and objects. - Sort arrays and objects.
- Transform JSON using [JMESPath](http://jmespath.org/) queries.
- Colorized code. - Colorized code.
- Color picker.
- Search & highlight text in the tree view. - Search & highlight text in the tree view.
- Undo and redo all actions. - Undo and redo all actions.
- JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)). - JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)).
@ -36,10 +37,12 @@ Cross browser testing for JSONEditor is generously provided by <a href="https://
- Colorized code (powered by [Ace](https://ace.c9.io)). - Colorized code (powered by [Ace](https://ace.c9.io)).
- Inspect JSON (powered by [Ace](https://ace.c9.io)). - Inspect JSON (powered by [Ace](https://ace.c9.io)).
- Format and compact JSON. - Format and compact JSON.
- Repair JSON.
- JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)). - JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)).
### Text editor ### Text editor
- Format and compact JSON. - Format and compact JSON.
- Repair JSON.
- JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)). - JSON schema validation (powered by [ajv](https://github.com/epoberezkin/ajv)).
@ -65,18 +68,6 @@ with bower:
bower install jsoneditor bower install jsoneditor
#### More
There is a directive available for using JSONEditor in AngularJS:
[https://github.com/isonet/angular-jsoneditor](https://github.com/isonet/angular-jsoneditor)
Directive for Angular 5.x as well:
[https://github.com/mariohmol/ang-jsoneditor](https://github.com/mariohmol/ang-jsoneditor)
## Use ## Use
```html ```html
@ -158,7 +149,7 @@ To create a custom bundle of the source code using browserify:
browserify ./index.js -o ./jsoneditor.custom.js -s JSONEditor browserify ./index.js -o ./jsoneditor.custom.js -s JSONEditor
The Ace editor, used in mode `code`, accounts for about 75% of the total The Ace editor, used in mode `code`, accounts for about one third of the total
size of the library. To exclude the Ace editor from the bundle: size of the library. To exclude the Ace editor from the bundle:
browserify ./index.js -o ./jsoneditor.custom.js -s JSONEditor -x brace -x brace/mode/json -x brace/ext/searchbox browserify ./index.js -o ./jsoneditor.custom.js -s JSONEditor -x brace -x brace/mode/json -x brace/ext/searchbox

View File

@ -300,6 +300,39 @@ Constructs a new JSONEditor.
``` ```
Only applicable when `mode` is 'form', 'tree' or 'view'. Only applicable when `mode` is 'form', 'tree' or 'view'.
- `{boolean} colorPicker`
If true (default), values containing a color name or color code will have a color picker rendered on their left side.
- `{function} onColorPicker(parent, color, onChange)`
Callback function triggered when the user clicks a color.
Can be used to implement a custom color picker.
The callback is invoked with three arguments:
`parent` is an HTML element where the color picker can be attached,
`color` is the current color,
`onChange(newColor)` is a callback which has to be invoked with the new color selected in the color picker.
JSONEditor comes with a built-in color picker, powered by [vanilla-picker](https://github.com/Sphinxxxx/vanilla-picker).
A simple example of `onColorPicker` using `vanilla-picker`:
```js
var options = {
onColorPicker: function (parent, color, onChange) {
new VanillaPicker({
parent: parent,
color: color,
onDone: function (color) {
onChange(color.hex)
}
}).show();
}
}
```
- `{string} language` - `{string} language`
The default language comes from the browser navigator, but you can force a specific language. So use here string as 'en' or 'pt-BR'. Built-in languages: `en`, `pt-BR`. Other translations can be specified via the option `languages`. The default language comes from the browser navigator, but you can force a specific language. So use here string as 'en' or 'pt-BR'. Built-in languages: `en`, `pt-BR`. Other translations can be specified via the option `languages`.
@ -543,12 +576,29 @@ valid JSON and the editor is in mode `tree`, `view`, or `form`.
Contents of the editor as string. Contents of the editor as string.
### Constants ### Static properties
- `{string[]} JSONEditor.VALID_OPTIONS` - `{string[]} JSONEditor.VALID_OPTIONS`
An array with the names of all known options. An array with the names of all known options.
- `{object} ace`
Access to the bundled Ace editor, via the [`brace` library](https://github.com/thlorenz/brace).
Ace is used in code mode.
Same as `var ace = require('brace');`.
- `{function} Ajv`
Access to the bundled [`ajv` library](https://github.com/epoberezkin/ajv), used for JSON schema validation.
Same as `var Ajv = require('ajv');`.
- `{function} VanillaPicker`
Access to the bundled [`vanilla-picker` library](https://github.com/Sphinxxxx/vanilla-picker), used as color picker.
Same as `var VanillaPicker = require('vanilla-picker');`.
### Examples ### Examples
A tree editor: A tree editor:

View File

@ -31,7 +31,7 @@
var json = { var json = {
'array': [1, 2, 3], 'array': [1, 2, 3],
'boolean': true, 'boolean': true,
'color': '#82B92C', 'color': '#82b92c',
'null': null, 'null': null,
'number': 123, 'number': 123,
'object': {'a': 'b', 'c': 'd'}, 'object': {'a': 'b', 'c': 'd'},

View File

@ -61,7 +61,8 @@ var compilerMinimalist = webpack({
plugins: [ plugins: [
bannerPlugin, bannerPlugin,
new webpack.IgnorePlugin(new RegExp('^brace$')), new webpack.IgnorePlugin(new RegExp('^brace$')),
new webpack.IgnorePlugin(new RegExp('^ajv')) new webpack.IgnorePlugin(new RegExp('^ajv')),
new webpack.IgnorePlugin(new RegExp('^vanilla-picker$'))
], ],
cache: true cache: true
}); });

19
package-lock.json generated
View File

@ -4,6 +4,11 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@sphinxxxx/color-conversion": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.1.1.tgz",
"integrity": "sha1-2igalkHrP2mZeUMv5TG7pSDj/7s="
},
"Base64": { "Base64": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
@ -810,6 +815,11 @@
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
"dev": true "dev": true
}, },
"drag-tracker": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/drag-tracker/-/drag-tracker-1.0.0.tgz",
"integrity": "sha1-m9M9OAvDBW22m9Wzz24GL+xYvWQ="
},
"duplexer2": { "duplexer2": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
@ -4220,6 +4230,15 @@
"user-home": "^1.1.1" "user-home": "^1.1.1"
} }
}, },
"vanilla-picker": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/vanilla-picker/-/vanilla-picker-2.3.0.tgz",
"integrity": "sha512-vteG997jlsJSNoPHOLoxThv6R1N8ZCbVTk5K3i5SXUolUQ5O26fG4NhxjSvpZeXbeiG0aVlg/cyQvGD7irJr0Q==",
"requires": {
"@sphinxxxx/color-conversion": "^2.1.1",
"drag-tracker": "^1.0.0"
}
},
"vinyl": { "vinyl": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz",

View File

@ -29,7 +29,8 @@
"jmespath": "0.15.0", "jmespath": "0.15.0",
"json-source-map": "^0.4.0", "json-source-map": "^0.4.0",
"mobius1-selectr": "2.4.1", "mobius1-selectr": "2.4.1",
"picomodal": "3.0.0" "picomodal": "3.0.0",
"vanilla-picker": "2.3.0"
}, },
"devDependencies": { "devDependencies": {
"gulp": "3.9.1", "gulp": "3.9.1",

View File

@ -124,7 +124,7 @@ div.jsoneditor-value.jsoneditor-invalid {
div.jsoneditor-tree button { div.jsoneditor-tree button.jsoneditor-button {
width: 24px; width: 24px;
height: 24px; height: 24px;
padding: 0; padding: 0;
@ -162,7 +162,7 @@ div.jsoneditor-tree *:focus {
outline: none; outline: none;
} }
div.jsoneditor-tree button:focus { div.jsoneditor-tree button.jsoneditor-button:focus {
/* TODO: nice outline for buttons with focus /* TODO: nice outline for buttons with focus
outline: #97B0F8 solid 2px; outline: #97B0F8 solid 2px;
box-shadow: 0 0 8px #97B0F8; box-shadow: 0 0 8px #97B0F8;
@ -206,6 +206,12 @@ div.jsoneditor-tree div.jsoneditor-color {
margin: 4px; margin: 4px;
border: 1px solid #808080; border: 1px solid #808080;
cursor: pointer;
}
div.jsoneditor-tree div.jsoneditor-color .picker_wrapper.popup.popup_bottom {
top: 28px;
left: -10px;
} }
div.jsoneditor { div.jsoneditor {

View File

@ -21,9 +21,10 @@ The minimalist version has excluded the following libraries:
- `ace` (via `brace`), used for the code editor. - `ace` (via `brace`), used for the code editor.
- `ajv`, used for JSON schema validation. - `ajv`, used for JSON schema validation.
- `vanilla-picker`, used as color picker.
This reduces the the size of the minified and gzipped JavaScript file from This reduces the the size of the minified and gzipped JavaScript file
about 160 kB to about 40 kB. from about 210 kB to about 70 kB (one third).
When to use the minimalist version? When to use the minimalist version?
@ -31,6 +32,8 @@ When to use the minimalist version?
- Or if you want to provide `ace` and/or `ajv` yourself via the configuration - Or if you want to provide `ace` and/or `ajv` yourself via the configuration
options, for example when you already use Ace in other parts of your options, for example when you already use Ace in other parts of your
web application too and don't want to bundle the library twice. web application too and don't want to bundle the library twice.
- You don't need the color picker, or want to provide your own
color picker using `onColorPicker`.
Which files are needed when using the minimalist version? Which files are needed when using the minimalist version?

View File

@ -8,6 +8,9 @@ catch (err) {
// no problem... when we need Ajv we will throw a neat exception // no problem... when we need Ajv we will throw a neat exception
} }
var ace = require('./ace'); // may be undefined in case of minimalist bundle
var VanillaPicker = require('./vanilla-picker'); // may be undefined in case of minimalist bundle
var treemode = require('./treemode'); var treemode = require('./treemode');
var textmode = require('./textmode'); var textmode = require('./textmode');
var util = require('./util'); var util = require('./util');
@ -442,4 +445,9 @@ JSONEditor.registerMode = function (mode) {
JSONEditor.registerMode(treemode); JSONEditor.registerMode(treemode);
JSONEditor.registerMode(textmode); JSONEditor.registerMode(textmode);
// expose some of the libraries that can be used customized
JSONEditor.ace = ace;
JSONEditor.Ajv = Ajv;
JSONEditor.VanillaPicker = VanillaPicker;
module.exports = JSONEditor; module.exports = JSONEditor;

View File

@ -758,7 +758,7 @@ Node.prototype.expand = function(recurse) {
// set this node expanded // set this node expanded
this.expanded = true; this.expanded = true;
if (this.dom.expand) { if (this.dom.expand) {
this.dom.expand.className = 'jsoneditor-expanded'; this.dom.expand.className = 'jsoneditor-button jsoneditor-expanded';
} }
this.showChilds(); this.showChilds();
@ -792,7 +792,7 @@ Node.prototype.collapse = function(recurse) {
// make this node collapsed // make this node collapsed
if (this.dom.expand) { if (this.dom.expand) {
this.dom.expand.className = 'jsoneditor-collapsed'; this.dom.expand.className = 'jsoneditor-button jsoneditor-collapsed';
} }
this.expanded = false; this.expanded = false;
}; };
@ -1737,7 +1737,11 @@ Node.prototype._updateDomValue = function () {
} }
// show color picker when value is a color // show color picker when value is a color
if (this.editable.value && typeof value === 'string' && util.isValidColor(value)) { if (this.editable.value &&
this.editor.options.colorPicker &&
typeof value === 'string' &&
util.isValidColor(value)) {
if (!this.dom.color) { if (!this.dom.color) {
this.dom.color = document.createElement('div'); this.dom.color = document.createElement('div');
this.dom.color.className = 'jsoneditor-color'; this.dom.color.className = 'jsoneditor-color';
@ -1754,11 +1758,7 @@ Node.prototype._updateDomValue = function () {
} }
else { else {
// cleanup color picker when displayed // cleanup color picker when displayed
if (this.dom.color) { this._deleteDomColor();
this.dom.tdColor.parentNode.removeChild(this.dom.tdColor);
delete this.dom.tdColor;
delete this.dom.color;
}
} }
// strip formatting from the contents of the editable div // strip formatting from the contents of the editable div
@ -1766,6 +1766,14 @@ Node.prototype._updateDomValue = function () {
} }
}; };
Node.prototype._deleteDomColor = function () {
if (this.dom.color) {
this.dom.tdColor.parentNode.removeChild(this.dom.tdColor);
delete this.dom.tdColor;
delete this.dom.color;
}
}
/** /**
* Update dom field: * Update dom field:
* - the text color of the field, depending on the text * - the text color of the field, depending on the text
@ -1918,7 +1926,7 @@ Node.prototype.getDom = function() {
var domDrag = document.createElement('button'); var domDrag = document.createElement('button');
domDrag.type = 'button'; domDrag.type = 'button';
dom.drag = domDrag; dom.drag = domDrag;
domDrag.className = 'jsoneditor-dragarea'; domDrag.className = 'jsoneditor-button jsoneditor-dragarea';
domDrag.title = translate('drag'); domDrag.title = translate('drag');
tdDrag.appendChild(domDrag); tdDrag.appendChild(domDrag);
} }
@ -1930,7 +1938,7 @@ Node.prototype.getDom = function() {
var menu = document.createElement('button'); var menu = document.createElement('button');
menu.type = 'button'; menu.type = 'button';
dom.menu = menu; dom.menu = menu;
menu.className = 'jsoneditor-contextmenu'; menu.className = 'jsoneditor-button jsoneditor-contextmenu';
menu.title = translate('actionsMenu'); menu.title = translate('actionsMenu');
tdMenu.appendChild(dom.menu); tdMenu.appendChild(dom.menu);
dom.tr.appendChild(tdMenu); dom.tr.appendChild(tdMenu);
@ -2638,11 +2646,13 @@ Node.prototype._createDomExpandButton = function () {
var expand = document.createElement('button'); var expand = document.createElement('button');
expand.type = 'button'; expand.type = 'button';
if (this._hasChilds()) { if (this._hasChilds()) {
expand.className = this.expanded ? 'jsoneditor-expanded' : 'jsoneditor-collapsed'; expand.className = this.expanded
? 'jsoneditor-button jsoneditor-expanded'
: 'jsoneditor-button jsoneditor-collapsed';
expand.title = translate('expandTitle'); expand.title = translate('expandTitle');
} }
else { else {
expand.className = 'jsoneditor-invisible'; expand.className = 'jsoneditor-button jsoneditor-invisible';
expand.title = ''; expand.title = '';
} }
@ -2753,6 +2763,10 @@ Node.prototype.onEvent = function (event) {
} }
} }
if (type === 'click' && (event.target === node.dom.tdColor || event.target === node.dom.color)) {
this._showColorPicker();
}
// swap the value of a boolean when the checkbox displayed left is clicked // swap the value of a boolean when the checkbox displayed left is clicked
if (type == 'change' && target == dom.checkbox) { if (type == 'change' && target == dom.checkbox) {
this.dom.value.innerHTML = !this.value; this.dom.value.innerHTML = !this.value;
@ -3300,6 +3314,31 @@ Node.prototype._onExpand = function (recurse) {
} }
}; };
/**
* Open a color picker to select a new color
* @private
*/
Node.prototype._showColorPicker = function () {
if (typeof this.editor.options.onColorPicker === 'function' && this.dom.color) {
var node = this;
// force deleting current color picker (if any)
node._deleteDomColor();
node.updateDom();
this.editor.options.onColorPicker(this.dom.color, this.value, function onChange(value) {
if (typeof value === 'string' && value !== node.value) {
// force recreating the color block, to cleanup any attached color picker
node._deleteDomColor();
node.value = value;
node.updateDom();
node._onChangeValue();
}
});
}
}
/** /**
* Remove nodes * Remove nodes
* @param {Node[] | Node} nodes * @param {Node[] | Node} nodes
@ -3762,7 +3801,7 @@ Node.prototype.getShowMoreDom = function () {
/** /**
* Find the node from an event target * Find the node from an event target
* @param {Node} target * @param {HTMLElement} target
* @return {Node | undefined} node or undefined when not found * @return {Node | undefined} node or undefined when not found
* @static * @static
*/ */
@ -3777,6 +3816,27 @@ Node.getNodeFromTarget = function (target) {
return undefined; return undefined;
}; };
/**
* Test whether target is a child of the color DOM of a node
* @param {HTMLElement} target
* @returns {boolean}
*/
Node.targetIsColorPicker = function (target) {
var node = Node.getNodeFromTarget(target);
if (node) {
var parent = target && target.parentNode;
while (parent) {
if (parent === node.dom.color) {
return true;
}
parent = parent.parentNode;
}
}
return false;
}
/** /**
* Remove the focus of given nodes, and move the focus to the (a) node before, * Remove the focus of given nodes, and move the focus to the (a) node before,
* (b) the node after, or (c) the parent node. * (b) the node after, or (c) the parent node.

View File

@ -55,7 +55,7 @@ function appendNodeFactory(Node) {
dom.tdMenu = tdMenu; dom.tdMenu = tdMenu;
var menu = document.createElement('button'); var menu = document.createElement('button');
menu.type = 'button'; menu.type = 'button';
menu.className = 'jsoneditor-contextmenu'; menu.className = 'jsoneditor-button jsoneditor-contextmenu';
menu.title = 'Click to open the actions menu (Ctrl+M)'; menu.title = 'Click to open the actions menu (Ctrl+M)';
dom.menu = menu; dom.menu = menu;
tdMenu.appendChild(dom.menu); tdMenu.appendChild(dom.menu);

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
var VanillaPicker = require('./vanilla-picker');
var Highlighter = require('./Highlighter'); var Highlighter = require('./Highlighter');
var History = require('./History'); var History = require('./History');
var SearchBox = require('./SearchBox'); var SearchBox = require('./SearchBox');
@ -139,6 +139,27 @@ treemode._setOptions = function (options) {
autocomplete: null, autocomplete: null,
navigationBar : true, navigationBar : true,
onSelectionChange: null, onSelectionChange: null,
colorPicker: true,
onColorPicker: function (parent, color, onChange) {
if (VanillaPicker) {
new VanillaPicker({
parent: parent,
color: color,
popup: 'bottom',
onDone: function (color) {
var alpha = color.rgba[3]
var hex = (alpha === 1)
? color.hex.substr(0, 7) // return #RRGGBB
: color.hex // return #RRGGBBAA
onChange(hex)
}
}).show();
}
else {
console.warn('Cannot open color picker: the `vanilla-picker` library is not included in the bundle. ' +
'Either use the full bundle or implement your own color picker using `onColorPicker`.')
}
},
onEvent: null onEvent: null
}; };
@ -1073,6 +1094,11 @@ treemode._onRedo = function () {
* @private * @private
*/ */
treemode._onEvent = function (event) { treemode._onEvent = function (event) {
// don't process events when coming from the color picker
if (Node.targetIsColorPicker(event.target)) {
return;
}
if (event.type === 'keydown') { if (event.type === 'keydown') {
this._onKeyDown(event); this._onKeyDown(event);
} }

View File

@ -0,0 +1,17 @@
var VanillaPicker
if (window.Picker) {
// use the already loaded instance of VanillaPicker
VanillaPicker = window.Picker
}
else {
try {
// load brace
VanillaPicker = require('vanilla-picker');
}
catch (err) {
// probably running the minimalist bundle
}
}
module.exports = VanillaPicker;

View File

@ -64,7 +64,7 @@
json = { json = {
"array": [1, 2, [3,4,5]], "array": [1, 2, [3,4,5]],
"boolean": true, "boolean": true,
"color": "#82B92C", "color": "#82b92c",
"htmlcode": '&quot;', "htmlcode": '&quot;',
"escaped_unicode": '\\u20b9', "escaped_unicode": '\\u20b9',
"unicode": '\u20b9,\uD83D\uDCA9', "unicode": '\u20b9,\uD83D\uDCA9',

View File

@ -50,7 +50,7 @@
json = { json = {
"array": [1, 2, 3], "array": [1, 2, 3],
"boolean": true, "boolean": true,
"color": "#82B92C", "color": "#82b92c",
"null": null, "null": null,
"number": 123, "number": 123,
"object": {"a": "b", "c": "d"}, "object": {"a": "b", "c": "d"},

View File

@ -50,7 +50,7 @@
json = { json = {
"array": [1, 2, 3], "array": [1, 2, 3],
"boolean": true, "boolean": true,
"color": "#82B92C", "color": "#82b92c",
"null": null, "null": null,
"number": 123, "number": 123,
"object": {"a": "b", "c": "d"}, "object": {"a": "b", "c": "d"},