Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Context Menu #284

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/js/JSONEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function JSONEditor (container, options, json) {
if (options) {
var VALID_OPTIONS = [
'ace', 'theme',
'ajv', 'schema',
'ajv', 'schema', 'context',
'onChange', 'onEditable', 'onError', 'onModeChange',
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'sortObjectKeys'
];
Expand Down
238 changes: 111 additions & 127 deletions src/js/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -1493,13 +1493,38 @@ Node.prototype.getDom = function() {
}
dom.tr.appendChild(tdDrag);

// create context menu
//Get the context menu options
var contextOptions = this.editor.options ? this.editor.options.context : undefined;
//Get the items with a defined context menu
var menuItems = (contextOptions) ? contextOptions.items : undefined;
//Get the items of which the children have a defined context menu
var menuChildren = (contextOptions) ? contextOptions.children : undefined;
//Check if there is any defined context for the current node
var itemWithContextMenu = (!menuItems && !menuChildren) || (menuItems && menuItems[this.field]);
//Check if there is any defined context for the parent of the current node
var childWithContextMenu = (!menuChildren && !menuItems) || (menuChildren && this.parent && menuChildren[this.parent.field]);

//Set a flag indicating that the current node has a context menu that must be displayed
this.displayContextMenu = itemWithContextMenu || childWithContextMenu;

var tdMenu = document.createElement('td');
var menu = document.createElement('button');
dom.menu = menu;
menu.className = 'jsoneditor-contextmenu';
menu.title = 'Click to open the actions menu (Ctrl+M)';
tdMenu.appendChild(dom.menu);

//Attach the context menu in the DOM
if(this.displayContextMenu) {
//Only the permitted menu actions will be displayed in the context menu
this.contextMenuActions = (itemWithContextMenu && menuItems) ?
menuItems[this.field] : (itemWithContextMenu && menuChildren) ? menuChildren[this.parent.field] : [];

var menu = document.createElement('button');
dom.menu = menu;
menu.className = 'jsoneditor-contextmenu';
menu.title = 'Click to open the actions menu (Ctrl+M)';
tdMenu.appendChild(dom.menu);
} else {
//Allow special theming for columns without context menu
//(e.g. reduce the width of the column)
tdMenu.className = 'jsoneditor-no-contextmenu';
}
dom.tr.appendChild(tdMenu);
}

Expand Down Expand Up @@ -3178,54 +3203,71 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
var node = this;
var titles = Node.TYPE_TITLES;
var items = [];
//Get the permitted context menu actions of the node
var menuActions = this.contextMenuActions ? this.contextMenuActions : [];
//Build the submenu once and reuse it where needed
var subMenu = [];
if(!menuActions || menuActions.indexOf('Auto') >= 0){
subMenu.push({
text: 'Auto',
className: 'jsoneditor-type-auto' +
(this.type == 'auto' ? ' jsoneditor-selected' : ''),
title: titles.auto,
click: function () {
node._onChangeType('auto');
}
});
}

if(!menuActions || menuActions.indexOf('Array') >= 0){
subMenu.push({
text: 'Array',
className: 'jsoneditor-type-array' +
(this.type == 'array' ? ' jsoneditor-selected' : ''),
title: titles.array,
click: function () {
node._onChangeType('array');
}
});
}

if(!menuActions || menuActions.indexOf('Object') >= 0){
subMenu.push({
text: 'Object',
className: 'jsoneditor-type-object' +
(this.type == 'object' ? ' jsoneditor-selected' : ''),
title: titles.object,
click: function () {
node._onChangeType('object');
}
});
}

if(!menuActions || menuActions.indexOf('String') >= 0){
subMenu.push({
text: 'String',
className: 'jsoneditor-type-string' +
(this.type == 'string' ? ' jsoneditor-selected' : ''),
title: titles.string,
click: function () {
node._onChangeType('string');
}
});
}

if (this.editable.value) {
//Attach the submenu in the respective first level menu items
subMenu = (subMenu.length > 0) ? subMenu : undefined;
if (this.editable.value && (!menuActions || menuActions.indexOf('Type') >= 0) ) {
items.push({
text: 'Type',
title: 'Change the type of this field',
className: 'jsoneditor-type-' + this.type,
submenu: [
{
text: 'Auto',
className: 'jsoneditor-type-auto' +
(this.type == 'auto' ? ' jsoneditor-selected' : ''),
title: titles.auto,
click: function () {
node._onChangeType('auto');
}
},
{
text: 'Array',
className: 'jsoneditor-type-array' +
(this.type == 'array' ? ' jsoneditor-selected' : ''),
title: titles.array,
click: function () {
node._onChangeType('array');
}
},
{
text: 'Object',
className: 'jsoneditor-type-object' +
(this.type == 'object' ? ' jsoneditor-selected' : ''),
title: titles.object,
click: function () {
node._onChangeType('object');
}
},
{
text: 'String',
className: 'jsoneditor-type-string' +
(this.type == 'string' ? ' jsoneditor-selected' : ''),
title: titles.string,
click: function () {
node._onChangeType('string');
}
}
]
submenu: subMenu

});
}

if (this._hasChilds()) {
if (this._hasChilds() && (!menuActions || menuActions.indexOf('Sort') >= 0) ) {
var direction = ((this.sortOrder == 'asc') ? 'desc': 'asc');
items.push({
text: 'Sort',
Expand Down Expand Up @@ -3265,7 +3307,7 @@ Node.prototype.showContextMenu = function (anchor, onClose) {

// create append button (for last child node only)
var childs = node.parent.childs;
if (node == childs[childs.length - 1]) {
if (node == childs[childs.length - 1] && (!menuActions || menuActions.indexOf('Append') >= 0) ) {
items.push({
text: 'Append',
title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
Expand All @@ -3274,89 +3316,26 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
click: function () {
node._onAppend('', '', 'auto');
},
submenu: [
{
text: 'Auto',
className: 'jsoneditor-type-auto',
title: titles.auto,
click: function () {
node._onAppend('', '', 'auto');
}
},
{
text: 'Array',
className: 'jsoneditor-type-array',
title: titles.array,
click: function () {
node._onAppend('', []);
}
},
{
text: 'Object',
className: 'jsoneditor-type-object',
title: titles.object,
click: function () {
node._onAppend('', {});
}
},
{
text: 'String',
className: 'jsoneditor-type-string',
title: titles.string,
click: function () {
node._onAppend('', '', 'string');
}
}
]
submenu: subMenu
});
}

// create insert button
items.push({
text: 'Insert',
title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
submenuTitle: 'Select the type of the field to be inserted',
className: 'jsoneditor-insert',
click: function () {
node._onInsertBefore('', '', 'auto');
},
submenu: [
{
text: 'Auto',
className: 'jsoneditor-type-auto',
title: titles.auto,
click: function () {
node._onInsertBefore('', '', 'auto');
}
},
{
text: 'Array',
className: 'jsoneditor-type-array',
title: titles.array,
click: function () {
node._onInsertBefore('', []);
}
},
{
text: 'Object',
className: 'jsoneditor-type-object',
title: titles.object,
click: function () {
node._onInsertBefore('', {});
}
if(!menuActions || menuActions.indexOf('Insert') >= 0) {
items.push({
text: 'Insert',
title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
submenuTitle: 'Select the type of the field to be inserted',
className: 'jsoneditor-insert',
click: function () {
node._onInsertBefore('', '', 'auto');
},
{
text: 'String',
className: 'jsoneditor-type-string',
title: titles.string,
click: function () {
node._onInsertBefore('', '', 'string');
}
}
]
});
submenu: subMenu
});
}

if (this.editable.field) {
//create duplicate button
if(this.editable.field && (!menuActions || menuActions.indexOf('Duplicate') >= 0)) {
// create duplicate button
items.push({
text: 'Duplicate',
Expand All @@ -3366,8 +3345,10 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
Node.onDuplicate(node);
}
});
}

// create remove button
// create remove button
if(this.editable.field && (!menuActions || menuActions.indexOf('Remove') >= 0)) {
items.push({
text: 'Remove',
title: 'Remove this field (Ctrl+Del)',
Expand All @@ -3379,8 +3360,11 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
}
}

var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor, this.editor.content);
//Display the context menu if there is one
if(items.length > 0) {
var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor, this.editor.content);
}
};

/**
Expand Down