diff --git a/src/js/JSONEditor.js b/src/js/JSONEditor.js index 7420d4951..9c9e71ebc 100644 --- a/src/js/JSONEditor.js +++ b/src/js/JSONEditor.js @@ -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' ]; diff --git a/src/js/Node.js b/src/js/Node.js index f7bcd8d85..1586db893 100644 --- a/src/js/Node.js +++ b/src/js/Node.js @@ -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); } @@ -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', @@ -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)', @@ -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', @@ -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)', @@ -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); + } }; /** diff --git a/src/js/appendNodeFactory.js b/src/js/appendNodeFactory.js index 48da1d335..916eb4117 100644 --- a/src/js/appendNodeFactory.js +++ b/src/js/appendNodeFactory.js @@ -48,14 +48,39 @@ function appendNodeFactory(Node) { // a cell for the dragarea column dom.tdDrag = document.createElement('td'); - // 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'); dom.tdMenu = tdMenu; - var menu = document.createElement('button'); - menu.className = 'jsoneditor-contextmenu'; - menu.title = 'Click to open the actions menu (Ctrl+M)'; - dom.menu = menu; - 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'); + menu.className = 'jsoneditor-contextmenu'; + menu.title = 'Click to open the actions menu (Ctrl+M)'; + dom.menu = menu; + 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'; + } } // a cell for the contents (showing text 'empty') @@ -133,9 +158,62 @@ function appendNodeFactory(Node) { AppendNode.prototype.showContextMenu = function (anchor, onClose) { var node = this; var titles = Node.TYPE_TITLES; - var items = [ - // create append button - { + //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'); + } + }); + } + + subMenu = (subMenu.length > 0) ? subMenu : undefined; + var items = []; + if(!menuActions || menuActions.indexOf('Append') >= 0){ + items.push({ 'text': 'Append', 'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', 'submenuTitle': 'Select the type of the field to be appended', @@ -143,45 +221,15 @@ function appendNodeFactory(Node) { '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 + }); + } - 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); + } }; /**