* 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
|
||||
* modes 'form', 'tree' and
|
||||
* '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
|
||||
* when a Node DOM is rendered. Function returns
|
||||
* a css class name to be set on a node.
|
||||
|
@ -170,6 +178,7 @@ JSONEditor.VALID_OPTIONS = [
|
|||
'onChange', 'onChangeJSON', 'onChangeText',
|
||||
'onEditable', 'onError', 'onEvent', 'onModeChange', 'onNodeName', 'onValidate', 'onCreateMenu',
|
||||
'onSelectionChange', 'onTextSelectionChange', 'onClassName',
|
||||
'onFocus', 'onBlur',
|
||||
'colorPicker', 'onColorPicker',
|
||||
'timestampTag',
|
||||
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
|
||||
|
|
|
@ -8,6 +8,7 @@ import { showSortModal } from './showSortModal'
|
|||
import { showTransformModal } from './showTransformModal'
|
||||
import { textModeMixins } from './textmode'
|
||||
import { DEFAULT_MODAL_ANCHOR, MAX_PREVIEW_CHARACTERS, PREVIEW_HISTORY_LIMIT, SIZE_LARGE } from './constants'
|
||||
import { FocusTracker } from './FocusTracker'
|
||||
import {
|
||||
addClassName,
|
||||
debounce,
|
||||
|
@ -78,6 +79,15 @@ previewmode.create = function (container, options = {}) {
|
|||
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.className = 'jsoneditor-outer'
|
||||
|
||||
|
@ -408,6 +418,9 @@ previewmode.destroy = function () {
|
|||
|
||||
this.history.clear()
|
||||
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 { showSortModal } from './showSortModal'
|
||||
import { showTransformModal } from './showTransformModal'
|
||||
import { FocusTracker } from './FocusTracker'
|
||||
import {
|
||||
addClassName,
|
||||
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
|
||||
if (this.options.enableSort) {
|
||||
const sort = document.createElement('button')
|
||||
|
@ -599,6 +609,9 @@ textmode.destroy = function () {
|
|||
this.textarea = 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 { Node } from './Node'
|
||||
import { ModeSwitcher } from './ModeSwitcher'
|
||||
import { FocusTracker } from './FocusTracker'
|
||||
import {
|
||||
addClassName,
|
||||
addEventListener,
|
||||
|
@ -103,6 +104,9 @@ treemode.destroy = function () {
|
|||
this.modeSwitcher.destroy()
|
||||
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
|
||||
this.frame = document.createElement('div')
|
||||
this.frame.className = 'jsoneditor jsoneditor-mode-' + this.options.mode
|
||||
// this.frame.setAttribute("tabindex","0");
|
||||
|
||||
this.container.appendChild(this.frame)
|
||||
|
||||
this.contentOuter = document.createElement('div')
|
||||
|
@ -902,6 +908,16 @@ treemode._createFrame = function () {
|
|||
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 => {
|
||||
const target = event.target// || event.srcElement;
|
||||
|
||||
|
@ -1498,12 +1514,21 @@ treemode._onKeyDown = function (event) {
|
|||
const metaKey = event.metaKey
|
||||
const shiftKey = event.shiftKey
|
||||
let handled = false
|
||||
const currentTarget = this.focusTarget
|
||||
|
||||
if (keynum === 9) { // Tab or Shift+Tab
|
||||
const me = this
|
||||
setTimeout(() => {
|
||||
/*
|
||||
- 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,12 @@
|
|||
onChangeText: function (text) {
|
||||
console.log('onChangeText', text);
|
||||
},
|
||||
onFocus: function(event) {
|
||||
console.log("Focus : ",event);
|
||||
},
|
||||
onBlur: function(event) {
|
||||
console.log("Blur : ",event);
|
||||
},
|
||||
indentation: 4,
|
||||
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