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:
|
||||
|
||||
```js
|
||||
var options = {
|
||||
const options = {
|
||||
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'.
|
||||
|
||||
- `{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`
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
var options = {
|
||||
const options = {
|
||||
mode: 'tree',
|
||||
search: true
|
||||
}
|
||||
var editor = new JSONEditor(container, options)
|
||||
var json = {
|
||||
const editor = new JSONEditor(container, options)
|
||||
let json = {
|
||||
"Array": [1, 2, 3],
|
||||
"Boolean": true,
|
||||
"Null": null,
|
||||
|
@ -245,18 +264,18 @@ var json = {
|
|||
editor.set(json)
|
||||
editor.expandAll()
|
||||
|
||||
var json = editor.get(json)
|
||||
json = editor.get(json)
|
||||
```
|
||||
|
||||
A text editor:
|
||||
|
||||
```js
|
||||
var options = {
|
||||
const options = {
|
||||
mode: 'text',
|
||||
indentation: 2
|
||||
}
|
||||
var editor = new JSONEditor(container, options)
|
||||
var json = {
|
||||
const editor = new JSONEditor(container, options)
|
||||
let json = {
|
||||
"Array": [1, 2, 3],
|
||||
"Boolean": true,
|
||||
"Null": null,
|
||||
|
@ -266,7 +285,7 @@ var json = {
|
|||
}
|
||||
editor.set(json)
|
||||
|
||||
var json = editor.get()
|
||||
json = editor.get()
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
```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:
|
||||
|
||||
```js
|
||||
var compactString = JSON.stringify(json)
|
||||
const compactString = JSON.stringify(json)
|
||||
```
|
||||
|
||||
To parse a String to a JSON object, use:
|
||||
|
||||
```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) {
|
||||
super(props)
|
||||
|
||||
// TODO: make key bindings customizable
|
||||
this.findKeyBinding = createFindKeyBinding(KEY_BINDINGS)
|
||||
|
||||
this.state = {
|
||||
text: '{}',
|
||||
compiledSchema: null
|
||||
|
@ -88,6 +85,14 @@ export default class TextMode extends Component {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -66,9 +66,6 @@ export default class TreeMode extends Component {
|
|||
|
||||
this.id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
|
||||
|
||||
// TODO: make key bindings customizable
|
||||
this.findKeyBinding = createFindKeyBinding(KEY_BINDINGS)
|
||||
|
||||
this.state = {
|
||||
data,
|
||||
|
||||
|
@ -88,7 +85,7 @@ export default class TreeMode extends Component {
|
|||
onExpand: this.handleExpand,
|
||||
|
||||
// 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: {
|
||||
|
@ -106,7 +103,7 @@ export default class TreeMode extends Component {
|
|||
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) {
|
||||
// Apply text
|
||||
if (nextProps.text !== currentProps.text) {
|
||||
|
@ -131,6 +128,14 @@ export default class TreeMode extends Component {
|
|||
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 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 */
|
||||
handleExpandAll = () => {
|
||||
const expanded = true
|
||||
|
|
|
@ -80,7 +80,11 @@
|
|||
alert(err)
|
||||
},
|
||||
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,
|
||||
escapeUnicode: true,
|
||||
history: true,
|
||||
|
|
|
@ -255,7 +255,8 @@ function jsoneditor (container, options = {}) {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
// 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.
|
||||
* 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
|
||||
const keyCombos = {}
|
||||
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)
|
||||
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
|
||||
|
||||
|
@ -165,3 +189,28 @@ const codes = {
|
|||
'221': ']',
|
||||
'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