* 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:
Survesh Jones 2019-11-25 00:11:08 +05:30 committed by Jos de Jong
parent a615f7ab91
commit 8894420263
7 changed files with 268 additions and 2 deletions

100
src/js/FocusTracker.js Normal file
View File

@ -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
}
}
}
}
}

View File

@ -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',

View File

@ -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()
}
/**

View File

@ -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()
}
/**

View File

@ -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(() => {
// 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)
}

View File

@ -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
};

View File

@ -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": '&quot;',
"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>