* Fix #727: exposing Focus and Blur events through JSONEditor options * Comments for onFocus and onBlur exposed events * Fixing lint issues * Fix for #727: Updated code for emitting onFocus and onBlur events * Seperating FocusTarget out as a seperate class * Fixing an issue that kept on setting the focus to the last element in the editor instead of passing it out * Moving the add() method of FocusTracker into its constructor and renaming its remove() to destroy() * removing a flag not needed anymore and making FocusTracker.target a required input * updating focus tracker's focus checking condition * Emitting onBlur callback when FocusTracker is being destroyed * fixing lint issues
This commit is contained in:
parent
a615f7ab91
commit
8894420263
|
@ -0,0 +1,100 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor FocusTracker
|
||||||
|
* A custom focus tracker for a DOM element with complex internal DOM structure
|
||||||
|
* @param {[Object]} config A set of configurations for the FocusTracker
|
||||||
|
* {DOM Object} target * The DOM object to track (required)
|
||||||
|
* {Function} onFocus onFocus callback
|
||||||
|
* {Function} onBlur onBlur callback
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class FocusTracker {
|
||||||
|
constructor (config) {
|
||||||
|
this.target = config.target || null
|
||||||
|
if (!this.target) {
|
||||||
|
throw new Error('FocusTracker constructor called without a "target" to track.')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onFocus = (typeof config.onFocus === 'function') ? config.onFocus : null
|
||||||
|
this.onBlur = (typeof config.onBlur === 'function') ? config.onBlur : null
|
||||||
|
this._onClick = this._onEvent.bind(this)
|
||||||
|
this._onKeyUp = function (event) {
|
||||||
|
if (event.which === 9 || event.keyCode === 9) {
|
||||||
|
this._onEvent(event)
|
||||||
|
}
|
||||||
|
}.bind(this)
|
||||||
|
|
||||||
|
this.focusFlag = false
|
||||||
|
this.firstEventFlag = true
|
||||||
|
|
||||||
|
/*
|
||||||
|
Adds required (click and keyup) event listeners to the 'document' object
|
||||||
|
to track the focus of the given 'target'
|
||||||
|
*/
|
||||||
|
if (this.onFocus || this.onBlur) {
|
||||||
|
document.addEventListener('click', this._onClick)
|
||||||
|
document.addEventListener('keyup', this._onKeyUp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the event listeners on the 'document' object
|
||||||
|
* that were added to track the focus of the given 'target'
|
||||||
|
*/
|
||||||
|
destroy () {
|
||||||
|
document.removeEventListener('click', this._onClick)
|
||||||
|
document.removeEventListener('keyup', this._onKeyUp)
|
||||||
|
this._onEvent({ target: document.body }) // calling _onEvent with body element in the hope that the FocusTracker is added to an element inside the body tag
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the focus of the target and calls the onFocus and onBlur
|
||||||
|
* event callbacks if available.
|
||||||
|
* @param {Event} [event] The 'click' or 'keyup' event object,
|
||||||
|
* from the respective events set on
|
||||||
|
* document object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
_onEvent (event) {
|
||||||
|
const target = event.target
|
||||||
|
let focusFlag
|
||||||
|
if (target === this.target) {
|
||||||
|
focusFlag = true
|
||||||
|
} else if (this.target.contains(target) || this.target.contains(document.activeElement)) {
|
||||||
|
focusFlag = true
|
||||||
|
} else {
|
||||||
|
focusFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusFlag) {
|
||||||
|
if (!this.focusFlag) {
|
||||||
|
// trigger the onFocus callback
|
||||||
|
if (this.onFocus) {
|
||||||
|
this.onFocus({ type: 'focus', target: this.target })
|
||||||
|
}
|
||||||
|
this.focusFlag = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.focusFlag || this.firstEventFlag) {
|
||||||
|
// trigger the onBlur callback
|
||||||
|
if (this.onBlur) {
|
||||||
|
this.onBlur({ type: 'blur', target: this.target })
|
||||||
|
}
|
||||||
|
this.focusFlag = false
|
||||||
|
|
||||||
|
/*
|
||||||
|
When switching from one mode to another in the editor, the FocusTracker gets recreated.
|
||||||
|
At that time, this.focusFlag will be init to 'false' and will fail the above if condition, when blur occurs
|
||||||
|
this.firstEventFlag is added to overcome that issue
|
||||||
|
*/
|
||||||
|
if (this.firstEventFlag) {
|
||||||
|
this.firstEventFlag = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,6 +77,14 @@ if (typeof Promise === 'undefined') {
|
||||||
* Only applicable for
|
* Only applicable for
|
||||||
* modes 'form', 'tree' and
|
* modes 'form', 'tree' and
|
||||||
* 'view'
|
* 'view'
|
||||||
|
* {function} onFocus Callback method, triggered
|
||||||
|
* when the editor comes into focus,
|
||||||
|
* passing an object {type, target},
|
||||||
|
* Applicable for all modes
|
||||||
|
* {function} onBlur Callback method, triggered
|
||||||
|
* when the editor goes out of focus,
|
||||||
|
* passing an object {type, target},
|
||||||
|
* Applicable for all modes
|
||||||
* {function} onClassName Callback method, triggered
|
* {function} onClassName Callback method, triggered
|
||||||
* when a Node DOM is rendered. Function returns
|
* when a Node DOM is rendered. Function returns
|
||||||
* a css class name to be set on a node.
|
* a css class name to be set on a node.
|
||||||
|
@ -170,6 +178,7 @@ JSONEditor.VALID_OPTIONS = [
|
||||||
'onChange', 'onChangeJSON', 'onChangeText',
|
'onChange', 'onChangeJSON', 'onChangeText',
|
||||||
'onEditable', 'onError', 'onEvent', 'onModeChange', 'onNodeName', 'onValidate', 'onCreateMenu',
|
'onEditable', 'onError', 'onEvent', 'onModeChange', 'onNodeName', 'onValidate', 'onCreateMenu',
|
||||||
'onSelectionChange', 'onTextSelectionChange', 'onClassName',
|
'onSelectionChange', 'onTextSelectionChange', 'onClassName',
|
||||||
|
'onFocus', 'onBlur',
|
||||||
'colorPicker', 'onColorPicker',
|
'colorPicker', 'onColorPicker',
|
||||||
'timestampTag',
|
'timestampTag',
|
||||||
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
|
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { showSortModal } from './showSortModal'
|
||||||
import { showTransformModal } from './showTransformModal'
|
import { showTransformModal } from './showTransformModal'
|
||||||
import { textModeMixins } from './textmode'
|
import { textModeMixins } from './textmode'
|
||||||
import { DEFAULT_MODAL_ANCHOR, MAX_PREVIEW_CHARACTERS, PREVIEW_HISTORY_LIMIT, SIZE_LARGE } from './constants'
|
import { DEFAULT_MODAL_ANCHOR, MAX_PREVIEW_CHARACTERS, PREVIEW_HISTORY_LIMIT, SIZE_LARGE } from './constants'
|
||||||
|
import { FocusTracker } from './FocusTracker'
|
||||||
import {
|
import {
|
||||||
addClassName,
|
addClassName,
|
||||||
debounce,
|
debounce,
|
||||||
|
@ -78,6 +79,15 @@ previewmode.create = function (container, options = {}) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setting the FocusTracker on 'this.frame' to track the editor's focus event
|
||||||
|
const focusTrackerConfig = {
|
||||||
|
target: this.frame,
|
||||||
|
onFocus: this.options.onFocus || null,
|
||||||
|
onBlur: this.options.onBlur || null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frameFocusTracker = new FocusTracker(focusTrackerConfig)
|
||||||
|
|
||||||
this.content = document.createElement('div')
|
this.content = document.createElement('div')
|
||||||
this.content.className = 'jsoneditor-outer'
|
this.content.className = 'jsoneditor-outer'
|
||||||
|
|
||||||
|
@ -408,6 +418,9 @@ previewmode.destroy = function () {
|
||||||
|
|
||||||
this.history.clear()
|
this.history.clear()
|
||||||
this.history = null
|
this.history = null
|
||||||
|
|
||||||
|
// Removing the FocusTracker set to track the editor's focus event
|
||||||
|
this.frameFocusTracker.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { ErrorTable } from './ErrorTable'
|
||||||
import { validateCustom } from './validationUtils'
|
import { validateCustom } from './validationUtils'
|
||||||
import { showSortModal } from './showSortModal'
|
import { showSortModal } from './showSortModal'
|
||||||
import { showTransformModal } from './showTransformModal'
|
import { showTransformModal } from './showTransformModal'
|
||||||
|
import { FocusTracker } from './FocusTracker'
|
||||||
import {
|
import {
|
||||||
addClassName,
|
addClassName,
|
||||||
debounce,
|
debounce,
|
||||||
|
@ -144,6 +145,15 @@ textmode.create = function (container, options = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setting the FocusTracker on 'this.frame' to track the editor's focus event
|
||||||
|
const focusTrackerConfig = {
|
||||||
|
target: this.frame,
|
||||||
|
onFocus: this.options.onFocus || null,
|
||||||
|
onBlur: this.options.onBlur || null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frameFocusTracker = new FocusTracker(focusTrackerConfig)
|
||||||
|
|
||||||
// create sort button
|
// create sort button
|
||||||
if (this.options.enableSort) {
|
if (this.options.enableSort) {
|
||||||
const sort = document.createElement('button')
|
const sort = document.createElement('button')
|
||||||
|
@ -599,6 +609,9 @@ textmode.destroy = function () {
|
||||||
this.textarea = null
|
this.textarea = null
|
||||||
|
|
||||||
this._debouncedValidate = null
|
this._debouncedValidate = null
|
||||||
|
|
||||||
|
// Removing the FocusTracker set to track the editor's focus event
|
||||||
|
this.frameFocusTracker.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { ContextMenu } from './ContextMenu'
|
||||||
import { TreePath } from './TreePath'
|
import { TreePath } from './TreePath'
|
||||||
import { Node } from './Node'
|
import { Node } from './Node'
|
||||||
import { ModeSwitcher } from './ModeSwitcher'
|
import { ModeSwitcher } from './ModeSwitcher'
|
||||||
|
import { FocusTracker } from './FocusTracker'
|
||||||
import {
|
import {
|
||||||
addClassName,
|
addClassName,
|
||||||
addEventListener,
|
addEventListener,
|
||||||
|
@ -103,6 +104,9 @@ treemode.destroy = function () {
|
||||||
this.modeSwitcher.destroy()
|
this.modeSwitcher.destroy()
|
||||||
this.modeSwitcher = null
|
this.modeSwitcher = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removing the FocusTracker set to track the editor's focus event
|
||||||
|
this.frameFocusTracker.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -888,6 +892,8 @@ treemode._createFrame = function () {
|
||||||
// create the frame
|
// create the frame
|
||||||
this.frame = document.createElement('div')
|
this.frame = document.createElement('div')
|
||||||
this.frame.className = 'jsoneditor jsoneditor-mode-' + this.options.mode
|
this.frame.className = 'jsoneditor jsoneditor-mode-' + this.options.mode
|
||||||
|
// this.frame.setAttribute("tabindex","0");
|
||||||
|
|
||||||
this.container.appendChild(this.frame)
|
this.container.appendChild(this.frame)
|
||||||
|
|
||||||
this.contentOuter = document.createElement('div')
|
this.contentOuter = document.createElement('div')
|
||||||
|
@ -902,6 +908,16 @@ treemode._createFrame = function () {
|
||||||
editor._onEvent(event)
|
editor._onEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setting the FocusTracker on 'this.frame' to track the editor's focus event
|
||||||
|
const focusTrackerConfig = {
|
||||||
|
target: this.frame,
|
||||||
|
onFocus: this.options.onFocus || null,
|
||||||
|
onBlur: this.options.onBlur || null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.frameFocusTracker = new FocusTracker(focusTrackerConfig)
|
||||||
|
|
||||||
this.frame.onclick = event => {
|
this.frame.onclick = event => {
|
||||||
const target = event.target// || event.srcElement;
|
const target = event.target// || event.srcElement;
|
||||||
|
|
||||||
|
@ -1498,12 +1514,21 @@ treemode._onKeyDown = function (event) {
|
||||||
const metaKey = event.metaKey
|
const metaKey = event.metaKey
|
||||||
const shiftKey = event.shiftKey
|
const shiftKey = event.shiftKey
|
||||||
let handled = false
|
let handled = false
|
||||||
|
const currentTarget = this.focusTarget
|
||||||
|
|
||||||
if (keynum === 9) { // Tab or Shift+Tab
|
if (keynum === 9) { // Tab or Shift+Tab
|
||||||
const me = this
|
const me = this
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// select all text when moving focus to an editable div
|
/*
|
||||||
selectContentEditable(me.focusTarget)
|
- Checking for change in focusTarget
|
||||||
|
- Without the check,
|
||||||
|
pressing tab after reaching the final DOM element in the editor will
|
||||||
|
set the focus back to it than passing focus outside the editor
|
||||||
|
*/
|
||||||
|
if (me.focusTarget !== currentTarget) {
|
||||||
|
// select all text when moving focus to an editable div
|
||||||
|
selectContentEditable(me.focusTarget)
|
||||||
|
}
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,12 @@
|
||||||
onChangeText: function (text) {
|
onChangeText: function (text) {
|
||||||
console.log('onChangeText', text);
|
console.log('onChangeText', text);
|
||||||
},
|
},
|
||||||
|
onFocus: function(event) {
|
||||||
|
console.log("Focus : ",event);
|
||||||
|
},
|
||||||
|
onBlur: function(event) {
|
||||||
|
console.log("Blur : ",event);
|
||||||
|
},
|
||||||
indentation: 4,
|
indentation: 4,
|
||||||
escapeUnicode: true
|
escapeUnicode: true
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||||
|
|
||||||
|
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
|
||||||
|
<script src="../dist/jsoneditor.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
font: 10.5pt arial;
|
||||||
|
color: #4d4d4d;
|
||||||
|
line-height: 150%;
|
||||||
|
width: 500px;
|
||||||
|
padding-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jsoneditor {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
display: block;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus {
|
||||||
|
border: 5px solid red;
|
||||||
|
}
|
||||||
|
.input:blur {
|
||||||
|
border: unset;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Switch editor mode using the mode box.
|
||||||
|
Note that the mode can be changed programmatically as well using the method
|
||||||
|
<code>editor.setMode(mode)</code>, try it in the console of your browser.
|
||||||
|
</p>
|
||||||
|
<input type="text" class="input">
|
||||||
|
<input type="text" class="input">
|
||||||
|
<form>
|
||||||
|
<div id="jsoneditor"></div>
|
||||||
|
</form>
|
||||||
|
<input type="text" class="input">
|
||||||
|
<input type="text" class="input">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var container, options, json, editor;
|
||||||
|
|
||||||
|
container = document.getElementById('jsoneditor');
|
||||||
|
|
||||||
|
options = {
|
||||||
|
mode: 'tree',
|
||||||
|
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
|
||||||
|
onError: function (err) {
|
||||||
|
alert(err.toString());
|
||||||
|
},
|
||||||
|
onFocus: function(event) {
|
||||||
|
container.style.border = '5px solid red';
|
||||||
|
console.log("Focus : ",event);
|
||||||
|
},
|
||||||
|
onBlur: function(event) {
|
||||||
|
container.style.border = 'unset';
|
||||||
|
console.log("Blur : ",event);
|
||||||
|
},
|
||||||
|
indentation: 4,
|
||||||
|
escapeUnicode: true
|
||||||
|
};
|
||||||
|
|
||||||
|
json = {
|
||||||
|
"array": [1, 2, [3,4,5]],
|
||||||
|
"boolean": true,
|
||||||
|
"color": "#82b92c",
|
||||||
|
"htmlcode": '"',
|
||||||
|
"escaped_unicode": '\\u20b9',
|
||||||
|
"unicode": '\u20b9,\uD83D\uDCA9',
|
||||||
|
"return": '\n',
|
||||||
|
"null": null,
|
||||||
|
"number": 123,
|
||||||
|
"object": {"a": "b", "c": "d"},
|
||||||
|
"string": "Hello World",
|
||||||
|
"timestamp": 1534952749890,
|
||||||
|
"url": "http://jsoneditoronline.org"
|
||||||
|
};
|
||||||
|
|
||||||
|
editorTest = new JSONEditor(container, options, json);
|
||||||
|
|
||||||
|
console.log('json', json);
|
||||||
|
console.log('string', JSON.stringify(json));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue