Implemented custom key bindings
This commit is contained in:
parent
239b702040
commit
8462bcda2c
43
docs/api.md
43
docs/api.md
|
@ -37,7 +37,7 @@ Constructs a new JSONEditor.
|
||||||
library used for JSON schema validation. Example:
|
library used for JSON schema validation. Example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var options = {
|
const options = {
|
||||||
ajv: Ajv({ allErrors: true, verbose: true })
|
ajv: Ajv({ allErrors: true, verbose: true })
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -72,6 +72,25 @@ Constructs a new JSONEditor.
|
||||||
|
|
||||||
Enables history, adds a button Undo and Redo to the menu of the JSONEditor. True by default. Only applicable when `mode` is 'tree' or 'form'.
|
Enables history, adds a button Undo and Redo to the menu of the JSONEditor. True by default. Only applicable when `mode` is 'tree' or 'form'.
|
||||||
|
|
||||||
|
- `{Object<String, String[]>} keyBindings`
|
||||||
|
|
||||||
|
Override default key bindings. For example to replace the binding to duplicate a node from `Ctrl+D` to `Ctrl+Shift+D`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const options = {
|
||||||
|
keyBindings: {
|
||||||
|
duplicate: ['Ctrl+Shift+D', 'Command+Shift+D']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It's important to define bindings for both Windows and Mac. The meta keys on Windows are `Ctrl`, `Shift`, `Alt`, and on Mac they are respectively `Command`, `Shift`, and `Option`.
|
||||||
|
|
||||||
|
All available key bindings are described on the page [Key Bindings](#key_bindings.md).
|
||||||
|
|
||||||
|
Key bindings are case insensitive.
|
||||||
|
|
||||||
|
|
||||||
- `{String} mode`
|
- `{String} mode`
|
||||||
|
|
||||||
Set the editor mode. Available values: 'tree' (default), 'view', 'form', 'code', 'text'. In 'view' mode, the data and datastructure is read-only. In 'form' mode, only the value can be changed, the datastructure is read-only. Mode 'code' requires the Ace editor to be loaded on the page. Mode 'text' shows the data as plain text.
|
Set the editor mode. Available values: 'tree' (default), 'view', 'form', 'code', 'text'. In 'view' mode, the data and datastructure is read-only. In 'form' mode, only the value can be changed, the datastructure is read-only. Mode 'code' requires the Ace editor to be loaded on the page. Mode 'text' shows the data as plain text.
|
||||||
|
@ -229,12 +248,12 @@ Get JSON data as string.
|
||||||
A tree editor:
|
A tree editor:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var options = {
|
const options = {
|
||||||
mode: 'tree',
|
mode: 'tree',
|
||||||
search: true
|
search: true
|
||||||
}
|
}
|
||||||
var editor = new JSONEditor(container, options)
|
const editor = new JSONEditor(container, options)
|
||||||
var json = {
|
let json = {
|
||||||
"Array": [1, 2, 3],
|
"Array": [1, 2, 3],
|
||||||
"Boolean": true,
|
"Boolean": true,
|
||||||
"Null": null,
|
"Null": null,
|
||||||
|
@ -245,18 +264,18 @@ var json = {
|
||||||
editor.set(json)
|
editor.set(json)
|
||||||
editor.expandAll()
|
editor.expandAll()
|
||||||
|
|
||||||
var json = editor.get(json)
|
json = editor.get(json)
|
||||||
```
|
```
|
||||||
|
|
||||||
A text editor:
|
A text editor:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var options = {
|
const options = {
|
||||||
mode: 'text',
|
mode: 'text',
|
||||||
indentation: 2
|
indentation: 2
|
||||||
}
|
}
|
||||||
var editor = new JSONEditor(container, options)
|
const editor = new JSONEditor(container, options)
|
||||||
var json = {
|
let json = {
|
||||||
"Array": [1, 2, 3],
|
"Array": [1, 2, 3],
|
||||||
"Boolean": true,
|
"Boolean": true,
|
||||||
"Null": null,
|
"Null": null,
|
||||||
|
@ -266,7 +285,7 @@ var json = {
|
||||||
}
|
}
|
||||||
editor.set(json)
|
editor.set(json)
|
||||||
|
|
||||||
var json = editor.get()
|
json = editor.get()
|
||||||
```
|
```
|
||||||
|
|
||||||
## JSON parsing and stringification
|
## JSON parsing and stringification
|
||||||
|
@ -274,17 +293,17 @@ var json = editor.get()
|
||||||
In general to parse or stringify JSON data, the browsers built in JSON parser can be used. To create a formatted string from a JSON object, use:
|
In general to parse or stringify JSON data, the browsers built in JSON parser can be used. To create a formatted string from a JSON object, use:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var formattedString = JSON.stringify(json, null, 2)
|
const formattedString = JSON.stringify(json, null, 2)
|
||||||
```
|
```
|
||||||
|
|
||||||
to create a compacted string from a JSON object, use:
|
to create a compacted string from a JSON object, use:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var compactString = JSON.stringify(json)
|
const compactString = JSON.stringify(json)
|
||||||
```
|
```
|
||||||
|
|
||||||
To parse a String to a JSON object, use:
|
To parse a String to a JSON object, use:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var json = JSON.parse(string)
|
const json = JSON.parse(string)
|
||||||
```
|
```
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Custom key bindings | JSONEditor</title>
|
||||||
|
|
||||||
|
<script src="../dist/jsoneditor.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 120%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Custom key bindings</h1>
|
||||||
|
<p>
|
||||||
|
In this example, the key bindings for <code>format</code> and <code>compact</code> are changed to respectively <code>Ctrl+Alt+1</code> and <code>Ctrl+Alt+2</code> instead of the default <code>Ctrl+\</code> and <code>Ctrl+Shift+\</code>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It's important to define key bindings for both Windows and Mac: <code>Command+Option+1</code> and <code>Command+Option+2</code> in this case.
|
||||||
|
</p>
|
||||||
|
<div id="jsoneditor"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// create the editor
|
||||||
|
var container = document.getElementById('jsoneditor')
|
||||||
|
var options = {
|
||||||
|
modes: ['code', 'text'],
|
||||||
|
keyBindings: {
|
||||||
|
compact: ['Ctrl+Alt+1', 'Command+Option+1'],
|
||||||
|
format: ['Ctrl+Alt+2', 'Command+Option+2']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var json = {
|
||||||
|
'array': [1, 2, 3],
|
||||||
|
'boolean': true,
|
||||||
|
'null': null,
|
||||||
|
'number': 123,
|
||||||
|
'object': {'a': 'b', 'c': 'd'},
|
||||||
|
'string': 'Hello World'
|
||||||
|
}
|
||||||
|
var editor = jsoneditor(container, options)
|
||||||
|
editor.set(json)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -54,9 +54,6 @@ export default class TextMode extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
// TODO: make key bindings customizable
|
|
||||||
this.findKeyBinding = createFindKeyBinding(KEY_BINDINGS)
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
text: '{}',
|
text: '{}',
|
||||||
compiledSchema: null
|
compiledSchema: null
|
||||||
|
@ -88,6 +85,14 @@ export default class TextMode extends Component {
|
||||||
this.setSchema(nextProps.schema)
|
this.setSchema(nextProps.schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply key bindings
|
||||||
|
if (!this.findKeyBinding ||
|
||||||
|
JSON.stringify(nextProps.keyBindings) !== JSON.stringify(currentProps.keyBindings)) {
|
||||||
|
// merge default and custom key bindings
|
||||||
|
const keyBindings = Object.assign({}, KEY_BINDINGS, nextProps.keyBindings)
|
||||||
|
this.findKeyBinding = createFindKeyBinding(keyBindings)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: apply patchText
|
// TODO: apply patchText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,9 +66,6 @@ export default class TreeMode extends Component {
|
||||||
|
|
||||||
this.id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
|
this.id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
|
||||||
|
|
||||||
// TODO: make key bindings customizable
|
|
||||||
this.findKeyBinding = createFindKeyBinding(KEY_BINDINGS)
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
data,
|
data,
|
||||||
|
|
||||||
|
@ -88,7 +85,7 @@ export default class TreeMode extends Component {
|
||||||
onExpand: this.handleExpand,
|
onExpand: this.handleExpand,
|
||||||
|
|
||||||
// TODO: now we're passing not just events but also other methods. reorganize this or rename 'state.events'
|
// TODO: now we're passing not just events but also other methods. reorganize this or rename 'state.events'
|
||||||
findKeyBinding: this.findKeyBinding
|
findKeyBinding: this.handleFindKeyBinding
|
||||||
},
|
},
|
||||||
|
|
||||||
search: {
|
search: {
|
||||||
|
@ -106,7 +103,7 @@ export default class TreeMode extends Component {
|
||||||
this.applyProps(nextProps, this.props)
|
this.applyProps(nextProps, this.props)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: create some sort of watcher structure for these props? Is there a Reactpattern for that?
|
// TODO: create some sort of watcher structure for these props? Is there a React pattern for that?
|
||||||
applyProps (nextProps, currentProps) {
|
applyProps (nextProps, currentProps) {
|
||||||
// Apply text
|
// Apply text
|
||||||
if (nextProps.text !== currentProps.text) {
|
if (nextProps.text !== currentProps.text) {
|
||||||
|
@ -131,6 +128,14 @@ export default class TreeMode extends Component {
|
||||||
this.setSchema(nextProps.schema)
|
this.setSchema(nextProps.schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply key bindings
|
||||||
|
if (!this.findKeyBinding ||
|
||||||
|
JSON.stringify(nextProps.keyBindings) !== JSON.stringify(currentProps.keyBindings)) {
|
||||||
|
// merge default and custom key bindings
|
||||||
|
const keyBindings = Object.assign({}, KEY_BINDINGS, nextProps.keyBindings)
|
||||||
|
this.findKeyBinding = createFindKeyBinding(keyBindings)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: apply patchText
|
// TODO: apply patchText
|
||||||
// TODO: apply patch
|
// TODO: apply patch
|
||||||
}
|
}
|
||||||
|
@ -370,6 +375,12 @@ export default class TreeMode extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @private */
|
||||||
|
handleFindKeyBinding = (event) => {
|
||||||
|
// findKeyBinding can change on the fly, so we can't bind it statically
|
||||||
|
return this.findKeyBinding (event)
|
||||||
|
}
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
handleExpandAll = () => {
|
handleExpandAll = () => {
|
||||||
const expanded = true
|
const expanded = true
|
||||||
|
|
|
@ -80,7 +80,11 @@
|
||||||
alert(err)
|
alert(err)
|
||||||
},
|
},
|
||||||
mode: mode,
|
mode: mode,
|
||||||
modes: ['text', 'code', 'tree', 'form', 'view'],
|
modes: ['text', 'code', 'tree', 'form', 'view'], keyBindings: {
|
||||||
|
compact: ['Ctrl+\\', 'Command+\\', 'Ctrl+Alt+1', 'Command+Option+1'],
|
||||||
|
format: ['Ctrl+Shift+\\', 'Command+Shift+\\', 'Ctrl+Alt+2', 'Command+Option+2'],
|
||||||
|
duplicate: ['Ctrl+D', 'Ctrl+Shift+D', 'Command+D', 'Command+Shift+D']
|
||||||
|
},
|
||||||
indentation: 4,
|
indentation: 4,
|
||||||
escapeUnicode: true,
|
escapeUnicode: true,
|
||||||
history: true,
|
history: true,
|
||||||
|
|
|
@ -255,7 +255,8 @@ function jsoneditor (container, options = {}) {
|
||||||
unmountComponentAtNode(editor._container)
|
unmountComponentAtNode(editor._container)
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.setMode(options && options.mode || 'tree')
|
const mode = options && options.mode || (options.modes && options.modes[0]) || 'tree';
|
||||||
|
editor.setMode(mode)
|
||||||
|
|
||||||
return editor
|
return editor
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// inspiration: https://github.com/andrepolischuk/keycomb
|
// inspiration: https://github.com/andrepolischuk/keycomb
|
||||||
|
|
||||||
|
// TODO: write unit tests for keyBindings
|
||||||
|
|
||||||
|
// FIXME: implement an escape sequence for the separator +
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a named key from a key code.
|
* Get a named key from a key code.
|
||||||
* For example:
|
* For example:
|
||||||
|
@ -44,16 +48,36 @@ export function createFindKeyBinding (keyBindings) {
|
||||||
// turn the map with key bindings by name (multiple per binding) into a map by key combo
|
// turn the map with key bindings by name (multiple per binding) into a map by key combo
|
||||||
const keyCombos = {}
|
const keyCombos = {}
|
||||||
Object.keys(keyBindings).forEach ((name) => {
|
Object.keys(keyBindings).forEach ((name) => {
|
||||||
keyBindings[name].forEach(combo => keyCombos[combo.toUpperCase()] = name)
|
keyBindings[name].forEach(combo => keyCombos[normalizeKeyCombo(combo)] = name)
|
||||||
})
|
})
|
||||||
|
|
||||||
return function findKeyBinding (event){
|
return function findKeyBinding (event) {
|
||||||
const keyCombo = keyComboFromEvent(event)
|
const keyCombo = keyComboFromEvent(event)
|
||||||
|
console.log('keyCombo', keyCombo)
|
||||||
|
|
||||||
return keyCombos[keyCombo.toUpperCase()] || null
|
return keyCombos[normalizeKeyCombo(keyCombo)] || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a key combo:
|
||||||
|
*
|
||||||
|
* - to upper case
|
||||||
|
* - replace aliases like "?" with "/"
|
||||||
|
*
|
||||||
|
* @param {string} combo
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
function normalizeKeyCombo (combo) {
|
||||||
|
const upper = combo.toUpperCase()
|
||||||
|
|
||||||
|
const last = upper[upper.length - 1]
|
||||||
|
if (last in aliases) {
|
||||||
|
return upper.substring(0, upper.length - 1) + aliases[last]
|
||||||
|
}
|
||||||
|
|
||||||
|
return upper
|
||||||
|
}
|
||||||
|
|
||||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
||||||
|
|
||||||
|
@ -165,3 +189,28 @@ const codes = {
|
||||||
'221': ']',
|
'221': ']',
|
||||||
'222': '\''
|
'222': '\''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all secondary characters of the keyboard buttons (used via Shift)
|
||||||
|
const aliases = {
|
||||||
|
'~': '`',
|
||||||
|
'!': '1',
|
||||||
|
'@': '2',
|
||||||
|
'#': '3',
|
||||||
|
'$': '4',
|
||||||
|
'%': '5',
|
||||||
|
'^': '6',
|
||||||
|
'&': '7',
|
||||||
|
'*': '8',
|
||||||
|
'(': '9',
|
||||||
|
')': '0',
|
||||||
|
'_': '-',
|
||||||
|
'+': '=',
|
||||||
|
'{': '[',
|
||||||
|
'}': ']',
|
||||||
|
'|': '\\',
|
||||||
|
':': ';',
|
||||||
|
'"': '',
|
||||||
|
'<': ',',
|
||||||
|
'>': '.',
|
||||||
|
'?': '/'
|
||||||
|
}
|
Loading…
Reference in New Issue