Implemented `popupAnchor` allowing to select a custom anchor element. See #869 and #870.

This commit is contained in:
jos 2019-12-18 17:26:16 +01:00
parent 036c9e1914
commit f86f3d4e1c
8 changed files with 177 additions and 14 deletions

View File

@ -3,8 +3,10 @@
https://github.com/josdejong/jsoneditor https://github.com/josdejong/jsoneditor
## not yet published, version 8.0.1 ## not yet published, version 8.1.0
- Implemented `popupAnchor` allowing to select a custom anchor element.
See #869 and #870.
- Fixed #502: CSS rule `* { font-family: ... }` resulting in Ace editor (`code` - Fixed #502: CSS rule `* { font-family: ... }` resulting in Ace editor (`code`
mode) not having a mono-space font anymore. mode) not having a mono-space font anymore.

View File

@ -567,6 +567,17 @@ Constructs a new JSONEditor.
The container element where modals (like for sorting and filtering) are attached: an overlay will be created on top The container element where modals (like for sorting and filtering) are attached: an overlay will be created on top
of this container, and the modal will be created in the center of this container. of this container, and the modal will be created in the center of this container.
- `{HTMLElement} popupAnchor`
The container element where popups (for example drop down menus, for JSON Schema error
tooltips, and color pickers) will be absolutely positioned.
By default, this is the root DIV element of the editor itself.
When the JSONEditor is inside a DIV element which hides overflowing contents
(CSS `overflow: auto` or `overflow: hidden`), tooltips will be visible only partly.
In this case, a `popupAnchor` outside of the element without hidden overflow will allow
the tooltips to be visible when overflowing the DIV element of the JSONEditor.
- `{boolean} enableSort` - `{boolean} enableSort`
Enable sorting of arrays and object properties. Only applicable for mode 'tree'. `true` by default. Enable sorting of arrays and object properties. Only applicable for mode 'tree'. `true` by default.

View File

@ -183,7 +183,8 @@ JSONEditor.VALID_OPTIONS = [
'timestampTag', 'timestampFormat', 'timestampTag', 'timestampFormat',
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform', 'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform',
'maxVisibleChilds', 'onValidationError' 'maxVisibleChilds', 'onValidationError',
'modalAnchor', 'popupAnchor'
] ]
/** /**

View File

@ -303,7 +303,7 @@ export class Node {
const createPopup = (destroyOnMouseOut) => { const createPopup = (destroyOnMouseOut) => {
const frame = this.editor.frame const frame = this.editor.frame
this.dom.popupAnchor = createAbsoluteAnchor(button, frame, onDestroy, destroyOnMouseOut) this.dom.popupAnchor = createAbsoluteAnchor(button, this.editor.getPopupAnchor(), onDestroy, destroyOnMouseOut)
const popupWidth = 200 // must correspond to what's configured in the CSS const popupWidth = 200 // must correspond to what's configured in the CSS
const buttonRect = button.getBoundingClientRect() const buttonRect = button.getBoundingClientRect()
@ -3021,7 +3021,7 @@ export class Node {
node._deleteDomColor() node._deleteDomColor()
node.updateDom() node.updateDom()
const colorAnchor = createAbsoluteAnchor(this.dom.color, this.editor.frame) const colorAnchor = createAbsoluteAnchor(this.dom.color, this.editor.getPopupAnchor())
this.editor.options.onColorPicker(colorAnchor, this.value, function onChange (value) { this.editor.options.onColorPicker(colorAnchor, this.value, function onChange (value) {
if (typeof value === 'string' && value !== node.value) { if (typeof value === 'string' && value !== node.value) {
@ -3835,7 +3835,7 @@ export class Node {
} }
const menu = new ContextMenu(items, { close: onClose }) const menu = new ContextMenu(items, { close: onClose })
menu.show(anchor, this.editor.frame) menu.show(anchor, this.editor.getPopupAnchor())
} }
/** /**

View File

@ -207,7 +207,7 @@ export function appendNodeFactory (Node) {
} }
const menu = new ContextMenu(items, { close: onClose }) const menu = new ContextMenu(items, { close: onClose })
menu.show(anchor, this.editor.frame) menu.show(anchor, this.editor.getPopupAnchor())
} }
/** /**

View File

@ -14,13 +14,13 @@ export function createAbsoluteAnchor (anchor, parent, onDestroy, destroyOnMouseO
const eventListeners = {} const eventListeners = {}
const anchorRect = anchor.getBoundingClientRect() const anchorRect = anchor.getBoundingClientRect()
const frameRect = parent.getBoundingClientRect() const parentRect = parent.getBoundingClientRect()
const absoluteAnchor = document.createElement('div') const absoluteAnchor = document.createElement('div')
absoluteAnchor.className = 'jsoneditor-anchor' absoluteAnchor.className = 'jsoneditor-anchor'
absoluteAnchor.style.position = 'absolute' absoluteAnchor.style.position = 'absolute'
absoluteAnchor.style.left = (anchorRect.left - frameRect.left) + 'px' absoluteAnchor.style.left = (anchorRect.left - parentRect.left) + 'px'
absoluteAnchor.style.top = (anchorRect.top - frameRect.top) + 'px' absoluteAnchor.style.top = (anchorRect.top - parentRect.top) + 'px'
absoluteAnchor.style.width = (anchorRect.width - 2) + 'px' absoluteAnchor.style.width = (anchorRect.width - 2) + 'px'
absoluteAnchor.style.height = (anchorRect.height - 2) + 'px' absoluteAnchor.style.height = (anchorRect.height - 2) + 'px'
absoluteAnchor.style.boxSizing = 'border-box' absoluteAnchor.style.boxSizing = 'border-box'

View File

@ -135,13 +135,12 @@ treemode._setOptions = function (options) {
const pickerHeight = 300 // estimated height of the color picker const pickerHeight = 300 // estimated height of the color picker
const top = parent.getBoundingClientRect().top const top = parent.getBoundingClientRect().top
const windowHeight = window.innerHeight const windowHeight = window.innerHeight
const showOnTop = ((windowHeight - top) < pickerHeight && top > pickerHeight)
new VanillaPicker({ new VanillaPicker({
parent: parent, parent: parent,
color: color, color: color,
popup: ((windowHeight - top) < pickerHeight && top > pickerHeight) popup: showOnTop ? 'top' : 'bottom',
? 'top'
: 'bottom',
onDone: function (color) { onDone: function (color) {
const alpha = color.rgba[3] const alpha = color.rgba[3]
const hex = (alpha === 1) const hex = (alpha === 1)
@ -1063,7 +1062,7 @@ treemode._createFrame = function () {
this.navBar.className = 'jsoneditor-navigation-bar nav-bar-empty' this.navBar.className = 'jsoneditor-navigation-bar nav-bar-empty'
this.frame.appendChild(this.navBar) this.frame.appendChild(this.navBar)
this.treePath = new TreePath(this.navBar, this.frame) this.treePath = new TreePath(this.navBar, this.getPopupAnchor())
this.treePath.onSectionSelected(this._onTreePathSectionSelected.bind(this)) this.treePath.onSectionSelected(this._onTreePathSectionSelected.bind(this))
this.treePath.onContextMenuItemSelected(this._onTreePathMenuItemSelected.bind(this)) this.treePath.onContextMenuItemSelected(this._onTreePathMenuItemSelected.bind(this))
} }
@ -1681,7 +1680,11 @@ treemode.showContextMenu = function (anchor, onClose) {
} }
const menu = new ContextMenu(items, { close: onClose }) const menu = new ContextMenu(items, { close: onClose })
menu.show(anchor, this.frame) menu.show(anchor, this.getPopupAnchor())
}
treemode.getPopupAnchor = function () {
return this.options.popupAnchor || this.frame
} }
/** /**

View File

@ -0,0 +1,146 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | JSON schema validation</title>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
width: 600px;
font: 11pt sans-serif;
}
#anchor {
position: relative;
}
#jsoneditor {
width: 320px;
height: 300px;
position: relative;
overflow: hidden;
}
/* custom bold styling for non-default JSON schema values */
.jsoneditor-is-not-default {
font-weight: bold;
}
</style>
</head>
<body>
<h1>Test custom tooltip anchor</h1>
<p>
The JSON Schema error tooltips and the color picker should have correct placing and overflow the editor, also in combination with scrolling.
</p>
<div id="anchor">
<div id="jsoneditor"></div>
</div>
<div style="height: 2000px"></div>
<script>
const schema = {
"title": "Employee",
"description": "Object containing employee details",
"type": "object",
"properties": {
"firstName": {
"title": "First Name",
"description": "The given name.",
"examples": [
"John"
],
"type": "string"
},
"lastName": {
"title": "Last Name",
"description": "The family name.",
"examples": [
"Smith"
],
"type": "string"
},
"gender": {
"title": "Gender",
"enum": ["male", "female"]
},
"availableToHire": {
"type": "boolean",
"default": false
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0,
"examples": [28, 32]
},
"job": {
"$ref": "job"
}
},
"required": ["firstName", "lastName"]
}
const job = {
"title": "Job description",
"type": "object",
"required": ["address"],
"properties": {
"company": {
"type": "string",
"examples": [
"ACME",
"Dexter Industries"
]
},
"role": {
"description": "Job title.",
"type": "string",
"examples": [
"Human Resources Coordinator",
"Software Developer"
],
"default": "Software Developer"
},
"address": {
"type": "string"
},
"salary": {
"type": "number",
"minimum": 120,
"examples": [100, 110, 120]
}
}
}
const json = {
firstName: 'John',
lastName: 'Doe',
gender: null,
age: "28",
availableToHire: true,
favoriteColor: 'red',
job: {
company: 'freelance',
role: 'developer',
salary: 100
}
}
const options = {
schema: schema,
schemaRefs: {"job": job},
mode: 'tree',
modes: ['code', 'text', 'tree', 'preview'],
popupAnchor: document.getElementById('anchor')
}
// create the editor
const container = document.getElementById('jsoneditor')
const editor = new JSONEditor(container, options, json)
editor.expandAll()
</script>
</body>
</html>