Fixed #104: context menus are now positioned relative to the elements of the editor instead of an absolute position in the window

This commit is contained in:
jos 2015-12-31 13:31:03 +01:00
parent 5955111b4d
commit 6b1c75b2c2
9 changed files with 94 additions and 60 deletions

View File

@ -30,6 +30,8 @@ https://github.com/josdejong/jsoneditor
- Fixed #38: clear search results after a new JSON object is set.
- Fixed #242: row stays highlighted when dragging outside editor.
- Fixed quick-keys Shift+Alt+Arrows not registering actions in history.
- Fixed #104: context menus are now positioned relative to the elements of the
editor instead of an absolute position in the window.
## 2015-06-13, version 4.2.1

View File

@ -1,6 +1,12 @@
/* ContextMenu - main menu */
div.jsoneditor-contextmenu-root {
position: relative;
width: 0;
height: 0;
}
div.jsoneditor-contextmenu {
position: absolute;
z-index: 99999;

View File

@ -142,7 +142,7 @@ div.jsoneditor-tree button.jsoneditor-contextmenu {
div.jsoneditor-tree button.jsoneditor-contextmenu:hover,
div.jsoneditor-tree button.jsoneditor-contextmenu:focus,
div.jsoneditor-tree button.jsoneditor-contextmenu.selected,
div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected,
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
background-position: -48px -48px;
}
@ -174,7 +174,7 @@ div.jsoneditor {
width: 100%;
height: 100%;
overflow: auto;
overflow: hidden;
position: relative;
padding: 0;
line-height: 100%;
@ -197,8 +197,6 @@ div.jsoneditor-outer {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
}
div.jsoneditor-tree {

View File

@ -4,7 +4,6 @@ div.jsoneditor-menu {
height: 35px;
padding: 2px;
margin: 0;
overflow: hidden;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
@ -14,7 +13,8 @@ div.jsoneditor-menu {
border-bottom: 1px solid #3883fa;
}
div.jsoneditor-menu > button {
div.jsoneditor-menu > button,
div.jsoneditor-menu > div.jsoneditor-modes > button {
width: 26px;
height: 26px;
margin: 2px;
@ -31,15 +31,19 @@ div.jsoneditor-menu > button {
float: left;
}
div.jsoneditor-menu > button:hover {
div.jsoneditor-menu > button:hover,
div.jsoneditor-menu > div.jsoneditor-modes > button:hover {
background-color: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.4);
}
div.jsoneditor-menu > button:focus,
div.jsoneditor-menu > button:active {
div.jsoneditor-menu > button:active,
div.jsoneditor-menu > div.jsoneditor-modes > button:focus,
div.jsoneditor-menu > div.jsoneditor-modes > button:active {
background-color: rgba(255,255,255,0.3);
}
div.jsoneditor-menu > button:disabled {
div.jsoneditor-menu > button:disabled,
div.jsoneditor-menu > div.jsoneditor-modes > button:disabled {
opacity: 0.5;
}
@ -68,14 +72,20 @@ div.jsoneditor-menu > button.jsoneditor-format {
background-position: -72px -120px;
}
div.jsoneditor-menu > button.jsoneditor-modes {
div.jsoneditor-menu > div.jsoneditor-modes {
display: inline-block;
float: left;
}
div.jsoneditor-menu > div.jsoneditor-modes > button {
background-image: none;
width: auto;
padding-left: 6px;
padding-right: 6px;
}
div.jsoneditor-menu > button.jsoneditor-separator {
div.jsoneditor-menu > button.jsoneditor-separator,
div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator {
margin-left: 10px;
}
@ -98,5 +108,3 @@ div.jsoneditor-menu a.jsoneditor-poweredBy {
top: 0;
padding: 10px;
}
/* TODO: css for button:disabled is not supported by IE8 */

View File

@ -18,13 +18,18 @@ function ContextMenu (items, options) {
this.items = items;
this.eventListeners = {};
this.selection = undefined; // holds the selection before the menu was opened
this.visibleSubmenu = undefined;
this.onClose = options ? options.close : undefined;
// create root element
var root = document.createElement('div');
root.className = 'jsoneditor-contextmenu-root';
dom.root = root;
// create a container element
var menu = document.createElement('div');
menu.className = 'jsoneditor-contextmenu';
dom.menu = menu;
root.appendChild(menu);
// create a list to hold the menu items
var list = document.createElement('ul');
@ -176,42 +181,54 @@ ContextMenu.visibleMenu = undefined;
/**
* Attach the menu to an anchor
* @param {HTMLElement} anchor
* @param {HTMLElement} anchor Anchor where the menu will be attached
* as sibling.
* @param {HTMLElement} [contentWindow] The DIV with with the (scrollable) contents
*/
ContextMenu.prototype.show = function (anchor) {
ContextMenu.prototype.show = function (anchor, contentWindow) {
this.hide();
// calculate whether the menu fits below the anchor
var windowHeight = window.innerHeight,
windowScroll = (window.pageYOffset || document.scrollTop || 0),
windowBottom = windowHeight + windowScroll,
anchorHeight = anchor.offsetHeight,
menuHeight = this.maxHeight;
// determine whether to display the menu below or above the anchor
var showBelow = true;
if (contentWindow) {
var anchorRect = anchor.getBoundingClientRect();
var contentRect = contentWindow.getBoundingClientRect();
if (anchorRect.bottom + this.maxHeight < contentRect.bottom) {
// fits below -> show below
}
else if (anchorRect.top - this.maxHeight > contentRect.top) {
// fits above -> show above
showBelow = false;
}
else {
// doesn't fit above nor below -> show below
}
}
// position the menu
var left = util.getAbsoluteLeft(anchor);
var top = util.getAbsoluteTop(anchor);
if (top + anchorHeight + menuHeight < windowBottom) {
if (showBelow) {
// display the menu below the anchor
this.dom.menu.style.left = left + 'px';
this.dom.menu.style.top = (top + anchorHeight) + 'px';
var anchorHeight = anchor.offsetHeight;
this.dom.menu.style.left = '0px';
this.dom.menu.style.top = anchorHeight + 'px';
this.dom.menu.style.bottom = '';
}
else {
// display the menu above the anchor
this.dom.menu.style.left = left + 'px';
this.dom.menu.style.left = '0px';
this.dom.menu.style.top = '';
this.dom.menu.style.bottom = (windowHeight - top) + 'px';
this.dom.menu.style.bottom = '0px';
}
// attach the menu to the document
document.body.appendChild(this.dom.menu);
// attach the menu to the parent of the anchor
var parent = anchor.parentNode;
parent.insertBefore(this.dom.root, parent.firstChild);
// create and attach event listeners
var me = this;
var list = this.dom.list;
this.eventListeners.mousedown = util.addEventListener(
document, 'mousedown', function (event) {
this.eventListeners.mousedown = util.addEventListener(window, 'mousedown', function (event) {
// hide menu on click outside of the menu
var target = event.target;
if ((target != list) && !me._isChildOf(target, list)) {
@ -220,14 +237,7 @@ ContextMenu.prototype.show = function (anchor) {
event.preventDefault();
}
});
this.eventListeners.mousewheel = util.addEventListener(
document, 'mousewheel', function (event) {
// block scrolling when context menu is visible
event.stopPropagation();
event.preventDefault();
});
this.eventListeners.keydown = util.addEventListener(
document, 'keydown', function (event) {
this.eventListeners.keydown = util.addEventListener(window, 'keydown', function (event) {
me._onKeyDown(event);
});
@ -249,8 +259,8 @@ ContextMenu.prototype.show = function (anchor) {
*/
ContextMenu.prototype.hide = function () {
// remove the menu from the DOM
if (this.dom.menu.parentNode) {
this.dom.menu.parentNode.removeChild(this.dom.menu);
if (this.dom.root.parentNode) {
this.dom.root.parentNode.removeChild(this.dom.root);
if (this.onClose) {
this.onClose();
}
@ -262,7 +272,7 @@ ContextMenu.prototype.hide = function () {
if (this.eventListeners.hasOwnProperty(name)) {
var fn = this.eventListeners[name];
if (fn) {
util.removeEventListener(document, name, fn);
util.removeEventListener(window, name, fn);
}
delete this.eventListeners[name];
}

View File

@ -2786,7 +2786,8 @@ Node.TYPE_TITLES = {
/**
* Show a contextmenu for this node
* @param {HTMLElement} anchor Anchor element to attache the context menu to.
* @param {HTMLElement} anchor Anchor element to attach the context menu to
* as sibling.
* @param {function} [onClose] Callback method called when the context menu
* is being closed.
*/
@ -2996,7 +2997,7 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
}
var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor);
menu.show(anchor, this.editor.content);
};
/**

View File

@ -179,7 +179,7 @@ function appendNodeFactory(Node) {
];
var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor);
menu.show(anchor, this.editor.content);
};
/**

View File

@ -94,7 +94,12 @@ function createModeSwitcher(editor, modes, current) {
menu.show(box);
};
return box;
var div = document.createElement('div');
div.className = 'jsoneditor-modes';
div.style.position = 'relative';
div.appendChild(box);
return div;
}
exports.create = createModeSwitcher;

View File

@ -653,7 +653,7 @@ treemode._onEvent = function (event) {
if (node && node.selected) {
if (event.type == 'click') {
if (event.target == node.dom.menu) {
this.showContextMenu(event.target);
this.showContextMenu(event.target.parentNode);
// stop propagation (else we will open the context menu of a single node)
return;
@ -701,6 +701,10 @@ treemode._startDragDistance = function (event) {
};
treemode._updateDragDistance = function (event) {
if (!this.dragDistanceEvent) {
this._startDragDistance(event);
}
var diffX = event.pageX - this.dragDistanceEvent.initialPageX;
var diffY = event.pageY - this.dragDistanceEvent.initialPageY;
@ -1024,7 +1028,7 @@ treemode.showContextMenu = function (anchor, onClose) {
});
var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor);
menu.show(anchor, this.content);
};