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 #38: clear search results after a new JSON object is set.
- Fixed #242: row stays highlighted when dragging outside editor. - Fixed #242: row stays highlighted when dragging outside editor.
- Fixed quick-keys Shift+Alt+Arrows not registering actions in history. - 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 ## 2015-06-13, version 4.2.1

View File

@ -1,6 +1,12 @@
/* ContextMenu - main menu */ /* ContextMenu - main menu */
div.jsoneditor-contextmenu-root {
position: relative;
width: 0;
height: 0;
}
div.jsoneditor-contextmenu { div.jsoneditor-contextmenu {
position: absolute; position: absolute;
z-index: 99999; 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:hover,
div.jsoneditor-tree button.jsoneditor-contextmenu:focus, 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 { tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
background-position: -48px -48px; background-position: -48px -48px;
} }
@ -174,7 +174,7 @@ div.jsoneditor {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: hidden;
position: relative; position: relative;
padding: 0; padding: 0;
line-height: 100%; line-height: 100%;
@ -197,8 +197,6 @@ div.jsoneditor-outer {
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
} }
div.jsoneditor-tree { div.jsoneditor-tree {

View File

@ -4,7 +4,6 @@ div.jsoneditor-menu {
height: 35px; height: 35px;
padding: 2px; padding: 2px;
margin: 0; margin: 0;
overflow: hidden;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
@ -14,7 +13,8 @@ div.jsoneditor-menu {
border-bottom: 1px solid #3883fa; border-bottom: 1px solid #3883fa;
} }
div.jsoneditor-menu > button { div.jsoneditor-menu > button,
div.jsoneditor-menu > div.jsoneditor-modes > button {
width: 26px; width: 26px;
height: 26px; height: 26px;
margin: 2px; margin: 2px;
@ -31,15 +31,19 @@ div.jsoneditor-menu > button {
float: left; 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); background-color: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.4); border: 1px solid rgba(255,255,255,0.4);
} }
div.jsoneditor-menu > button:focus, 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); 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; opacity: 0.5;
} }
@ -68,14 +72,20 @@ div.jsoneditor-menu > button.jsoneditor-format {
background-position: -72px -120px; 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; background-image: none;
width: auto; width: auto;
padding-left: 6px; padding-left: 6px;
padding-right: 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; margin-left: 10px;
} }
@ -98,5 +108,3 @@ div.jsoneditor-menu a.jsoneditor-poweredBy {
top: 0; top: 0;
padding: 10px; 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.items = items;
this.eventListeners = {}; this.eventListeners = {};
this.selection = undefined; // holds the selection before the menu was opened this.selection = undefined; // holds the selection before the menu was opened
this.visibleSubmenu = undefined;
this.onClose = options ? options.close : 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 // create a container element
var menu = document.createElement('div'); var menu = document.createElement('div');
menu.className = 'jsoneditor-contextmenu'; menu.className = 'jsoneditor-contextmenu';
dom.menu = menu; dom.menu = menu;
root.appendChild(menu);
// create a list to hold the menu items // create a list to hold the menu items
var list = document.createElement('ul'); var list = document.createElement('ul');
@ -176,42 +181,54 @@ ContextMenu.visibleMenu = undefined;
/** /**
* Attach the menu to an anchor * 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(); this.hide();
// calculate whether the menu fits below the anchor // determine whether to display the menu below or above the anchor
var windowHeight = window.innerHeight, var showBelow = true;
windowScroll = (window.pageYOffset || document.scrollTop || 0), if (contentWindow) {
windowBottom = windowHeight + windowScroll, var anchorRect = anchor.getBoundingClientRect();
anchorHeight = anchor.offsetHeight, var contentRect = contentWindow.getBoundingClientRect();
menuHeight = this.maxHeight;
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 // position the menu
var left = util.getAbsoluteLeft(anchor); if (showBelow) {
var top = util.getAbsoluteTop(anchor);
if (top + anchorHeight + menuHeight < windowBottom) {
// display the menu below the anchor // display the menu below the anchor
this.dom.menu.style.left = left + 'px'; var anchorHeight = anchor.offsetHeight;
this.dom.menu.style.top = (top + anchorHeight) + 'px'; this.dom.menu.style.left = '0px';
this.dom.menu.style.top = anchorHeight + 'px';
this.dom.menu.style.bottom = ''; this.dom.menu.style.bottom = '';
} }
else { else {
// display the menu above the anchor // 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.top = '';
this.dom.menu.style.bottom = (windowHeight - top) + 'px'; this.dom.menu.style.bottom = '0px';
} }
// attach the menu to the document // attach the menu to the parent of the anchor
document.body.appendChild(this.dom.menu); var parent = anchor.parentNode;
parent.insertBefore(this.dom.root, parent.firstChild);
// create and attach event listeners // create and attach event listeners
var me = this; var me = this;
var list = this.dom.list; var list = this.dom.list;
this.eventListeners.mousedown = util.addEventListener( this.eventListeners.mousedown = util.addEventListener(window, 'mousedown', function (event) {
document, 'mousedown', function (event) {
// hide menu on click outside of the menu // hide menu on click outside of the menu
var target = event.target; var target = event.target;
if ((target != list) && !me._isChildOf(target, list)) { if ((target != list) && !me._isChildOf(target, list)) {
@ -220,14 +237,7 @@ ContextMenu.prototype.show = function (anchor) {
event.preventDefault(); event.preventDefault();
} }
}); });
this.eventListeners.mousewheel = util.addEventListener( this.eventListeners.keydown = util.addEventListener(window, 'keydown', function (event) {
document, 'mousewheel', function (event) {
// block scrolling when context menu is visible
event.stopPropagation();
event.preventDefault();
});
this.eventListeners.keydown = util.addEventListener(
document, 'keydown', function (event) {
me._onKeyDown(event); me._onKeyDown(event);
}); });
@ -249,8 +259,8 @@ ContextMenu.prototype.show = function (anchor) {
*/ */
ContextMenu.prototype.hide = function () { ContextMenu.prototype.hide = function () {
// remove the menu from the DOM // remove the menu from the DOM
if (this.dom.menu.parentNode) { if (this.dom.root.parentNode) {
this.dom.menu.parentNode.removeChild(this.dom.menu); this.dom.root.parentNode.removeChild(this.dom.root);
if (this.onClose) { if (this.onClose) {
this.onClose(); this.onClose();
} }
@ -262,7 +272,7 @@ ContextMenu.prototype.hide = function () {
if (this.eventListeners.hasOwnProperty(name)) { if (this.eventListeners.hasOwnProperty(name)) {
var fn = this.eventListeners[name]; var fn = this.eventListeners[name];
if (fn) { if (fn) {
util.removeEventListener(document, name, fn); util.removeEventListener(window, name, fn);
} }
delete this.eventListeners[name]; delete this.eventListeners[name];
} }

View File

@ -2786,7 +2786,8 @@ Node.TYPE_TITLES = {
/** /**
* Show a contextmenu for this node * 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 * @param {function} [onClose] Callback method called when the context menu
* is being closed. * is being closed.
*/ */
@ -2996,7 +2997,7 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
} }
var menu = new ContextMenu(items, {close: 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}); 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); 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; exports.create = createModeSwitcher;

View File

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