From d7551da7e6b328a304d02709de89121cda0a8769 Mon Sep 17 00:00:00 2001 From: Tanmay Rajani Date: Tue, 13 Nov 2018 00:41:09 +0530 Subject: [PATCH] Create option to hide the blue main menu bar on top (#596) * ability to hide main menu bar; minor fixes for typos, JSDoc etc. * fix CSS issue when mainMenuBar is hidden in all modes; other minor refactoring --- docs/api.md | 4 + src/css/jsoneditor.css | 22 ++-- src/js/JSONEditor.js | 2 +- src/js/Node.js | 6 +- src/js/textmode.js | 196 ++++++++++++++++++----------------- src/js/treemode.js | 228 ++++++++++++++++++++--------------------- 6 files changed, 238 insertions(+), 220 deletions(-) diff --git a/docs/api.md b/docs/api.md index adafe71..bb71a15 100644 --- a/docs/api.md +++ b/docs/api.md @@ -234,6 +234,10 @@ Constructs a new JSONEditor. - Can return an object `{startFrom: number, options: string[]}`. Here `startFrom` determines the start character from where the existing text will be replaced. `startFrom` is `0` by default, replacing the whole text. - Can return a `Promise` resolving one of the return types above to support asynchronously retrieving a list with options. +- `{boolean} mainMenuBar` + + Adds main menu bar - Contains format, sort, transform, search etc. functionality. True by default. Applicable in all types of `mode`. + - `{boolean} navigationBar` Adds navigation bar to the menu - the navigation bar visualize the current position on the tree structure as well as allows breadcrumbs navigation. True by default. Only applicable when `mode` is 'tree', 'form' or 'view'. diff --git a/src/css/jsoneditor.css b/src/css/jsoneditor.css index dd537f5..1b6268a 100644 --- a/src/css/jsoneditor.css +++ b/src/css/jsoneditor.css @@ -253,8 +253,8 @@ div.jsoneditor-outer { position: static; width: 100%; height: 100%; - margin: -35px 0 0 0; - padding: 35px 0 0 0; + margin: 0; + padding: 0; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -262,13 +262,23 @@ div.jsoneditor-outer { } div.jsoneditor-outer.has-nav-bar { - margin: -61px 0 0 0; - padding: 61px 0 0 0; + margin-top: -26px; + padding-top: 26px; } div.jsoneditor-outer.has-status-bar { - margin: -35px 0 -26px 0; - padding: 35px 0 26px 0; + margin-bottom: -26px; + padding-bottom: 26px; +} + +div.jsoneditor-outer.has-main-menu-bar { + margin-top: -35px; + padding-top: 35px; +} + +div.jsoneditor-outer.has-nav-bar.has-main-menu-bar { + margin-top: -61px; + padding-top: 61px; } textarea.jsoneditor-text, diff --git a/src/js/JSONEditor.js b/src/js/JSONEditor.js index db84ed5..ba38d29 100644 --- a/src/js/JSONEditor.js +++ b/src/js/JSONEditor.js @@ -167,7 +167,7 @@ JSONEditor.VALID_OPTIONS = [ 'colorPicker', 'onColorPicker', 'timestampTag', 'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', - 'sortObjectKeys', 'navigationBar', 'statusBar', 'languages', 'language', 'enableSort', 'enableTransform' + 'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform' ]; /** diff --git a/src/js/Node.js b/src/js/Node.js index 9e2d34a..1305cc9 100644 --- a/src/js/Node.js +++ b/src/js/Node.js @@ -1396,7 +1396,7 @@ Node.prototype.changeType = function (newType) { this.childs = []; } - this.childs.forEach(function (child, index) { + this.childs.forEach(function (child) { child.clearDom(); delete child.index; child.fieldEditable = true; @@ -3372,7 +3372,7 @@ Node.prototype._showColorPicker = function () { } }); } -} +}; /** * Remove nodes @@ -3870,7 +3870,7 @@ Node.targetIsColorPicker = function (target) { } return false; -} +}; /** * Remove the focus of given nodes, and move the focus to the (a) node before, diff --git a/src/js/textmode.js b/src/js/textmode.js index 9acfc6f..59eb583 100644 --- a/src/js/textmode.js +++ b/src/js/textmode.js @@ -41,10 +41,13 @@ textmode.create = function (container, options) { // read options options = options || {}; - if(typeof options.statusBar === 'undefined') { + if (typeof options.statusBar === 'undefined') { options.statusBar = true; } + // setting default for textmode + options.mainMenuBar = options.mainMenuBar !== false; + this.options = options; // indentation @@ -108,67 +111,89 @@ textmode.create = function (container, options) { this.frame.onkeydown = function (event) { me._onKeyDown(event); }; - - // create menu - this.menu = document.createElement('div'); - this.menu.className = 'jsoneditor-menu'; - this.frame.appendChild(this.menu); - // create format button - var buttonFormat = document.createElement('button'); - buttonFormat.type = 'button'; - buttonFormat.className = 'jsoneditor-format'; - buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'; - this.menu.appendChild(buttonFormat); - buttonFormat.onclick = function () { - try { - me.format(); - me._onChange(); - } - catch (err) { - me._onError(err); - } - }; + this.content = document.createElement('div'); + this.content.className = 'jsoneditor-outer'; - // create compact button - var buttonCompact = document.createElement('button'); - buttonCompact.type = 'button'; - buttonCompact.className = 'jsoneditor-compact'; - buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'; - this.menu.appendChild(buttonCompact); - buttonCompact.onclick = function () { - try { - me.compact(); - me._onChange(); - } - catch (err) { - me._onError(err); - } - }; + if (this.options.mainMenuBar) { + util.addClassName(this.content, 'has-main-menu-bar'); - // create repair button - var buttonRepair = document.createElement('button'); - buttonRepair.type = 'button'; - buttonRepair.className = 'jsoneditor-repair'; - buttonRepair.title = 'Repair JSON: fix quotes and escape characters, remove comments and JSONP notation, turn JavaScript objects into JSON.'; - this.menu.appendChild(buttonRepair); - buttonRepair.onclick = function () { - try { - me.repair(); - me._onChange(); - } - catch (err) { - me._onError(err); - } - }; + // create menu + this.menu = document.createElement('div'); + this.menu.className = 'jsoneditor-menu'; + this.frame.appendChild(this.menu); - // create mode box - if (this.options && this.options.modes && this.options.modes.length) { - this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) { - // switch mode and restore focus - me.setMode(mode); - me.modeSwitcher.focus(); - }); + // create format button + var buttonFormat = document.createElement('button'); + buttonFormat.type = 'button'; + buttonFormat.className = 'jsoneditor-format'; + buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'; + this.menu.appendChild(buttonFormat); + buttonFormat.onclick = function () { + try { + me.format(); + me._onChange(); + } + catch (err) { + me._onError(err); + } + }; + + // create compact button + var buttonCompact = document.createElement('button'); + buttonCompact.type = 'button'; + buttonCompact.className = 'jsoneditor-compact'; + buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'; + this.menu.appendChild(buttonCompact); + buttonCompact.onclick = function () { + try { + me.compact(); + me._onChange(); + } + catch (err) { + me._onError(err); + } + }; + + // create repair button + var buttonRepair = document.createElement('button'); + buttonRepair.type = 'button'; + buttonRepair.className = 'jsoneditor-repair'; + buttonRepair.title = 'Repair JSON: fix quotes and escape characters, remove comments and JSONP notation, turn JavaScript objects into JSON.'; + this.menu.appendChild(buttonRepair); + buttonRepair.onclick = function () { + try { + me.repair(); + me._onChange(); + } + catch (err) { + me._onError(err); + } + }; + + // create mode box + if (this.options && this.options.modes && this.options.modes.length) { + this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) { + // switch mode and restore focus + me.setMode(mode); + me.modeSwitcher.focus(); + }); + } + + if (this.mode == 'code') { + var poweredBy = document.createElement('a'); + poweredBy.appendChild(document.createTextNode('powered by ace')); + poweredBy.href = 'http://ace.ajax.org'; + poweredBy.target = '_blank'; + poweredBy.className = 'jsoneditor-poweredBy'; + poweredBy.onclick = function () { + // TODO: this anchor falls below the margin of the content, + // therefore the normal a.href does not work. We use a click event + // for now, but this should be fixed. + window.open(poweredBy.href, poweredBy.target); + }; + this.menu.appendChild(poweredBy); + } } var emptyNode = {}; @@ -176,10 +201,7 @@ textmode.create = function (container, options) { && typeof(this.options.onEditable === 'function') && !this.options.onEditable(emptyNode)); - this.content = document.createElement('div'); - this.content.className = 'jsoneditor-outer'; this.frame.appendChild(this.content); - this.container.appendChild(this.frame); if (this.mode == 'code') { @@ -224,19 +246,6 @@ textmode.create = function (container, options) { }); } - var poweredBy = document.createElement('a'); - poweredBy.appendChild(document.createTextNode('powered by ace')); - poweredBy.href = 'http://ace.ajax.org'; - poweredBy.target = '_blank'; - poweredBy.className = 'jsoneditor-poweredBy'; - poweredBy.onclick = function () { - // TODO: this anchor falls below the margin of the content, - // therefore the normal a.href does not work. We use a click event - // for now, but this should be fixed. - window.open(poweredBy.href, poweredBy.target); - }; - this.menu.appendChild(poweredBy); - // register onchange event aceEditor.on('change', this._onChange.bind(this)); aceEditor.on('changeSelection', this._onSelect.bind(this)); @@ -269,12 +278,12 @@ textmode.create = function (container, options) { this.dom.validationErrorsContainer = validationErrorsContainer; this.frame.appendChild(validationErrorsContainer); - var additinalErrorsIndication = document.createElement('div'); - additinalErrorsIndication.style.display = 'none'; - additinalErrorsIndication.className = "jsoneditor-additional-errors fadein"; - additinalErrorsIndication.innerHTML = "Scroll for more ▿"; - this.dom.additinalErrorsIndication = additinalErrorsIndication; - validationErrorsContainer.appendChild(additinalErrorsIndication); + var additionalErrorsIndication = document.createElement('div'); + additionalErrorsIndication.style.display = 'none'; + additionalErrorsIndication.className = "jsoneditor-additional-errors fadein"; + additionalErrorsIndication.innerHTML = "Scroll for more ▿"; + this.dom.additionalErrorsIndication = additionalErrorsIndication; + validationErrorsContainer.appendChild(additionalErrorsIndication); if (options.statusBar) { util.addClassName(this.content, 'has-status-bar'); @@ -428,20 +437,18 @@ textmode._onKeyDown = function (event) { /** * Event handler for mousedown. - * @param {Event} event * @private */ -textmode._onMouseDown = function (event) { +textmode._onMouseDown = function () { this._updateCursorInfo(); this._emitSelectionChange(); }; /** * Event handler for blur. - * @param {Event} event * @private */ -textmode._onBlur = function (event) { +textmode._onBlur = function () { var me = this; // this allows to avoid blur when clicking inner elements (like the errors panel) // just make sure to set the isFocused to true on the inner element onclick callback @@ -481,7 +488,7 @@ textmode._updateCursorInfo = function () { line: line, column: col, count: count - } + }; if(me.options.statusBar) { updateDisplay(); @@ -500,7 +507,7 @@ textmode._updateCursorInfo = function () { line: line, column: col, count: count - } + }; if(this.options.statusBar) { updateDisplay(); @@ -528,7 +535,7 @@ textmode._emitSelectionChange = function () { var currentSelection = this.getTextSelection(); this._selectionChangedHandler(currentSelection.start, currentSelection.end, currentSelection.text); } -} +}; /** * refresh ERROR annotations state @@ -543,7 +550,7 @@ textmode._refreshAnnotations = function () { var errEnnotations = session.getAnnotations().filter(function(annotation) {return annotation.type === 'error' }); session.setAnnotations(errEnnotations); } -} +}; /** * Destroy the editor. Clean up DOM, event listeners, and web workers. @@ -848,7 +855,7 @@ textmode._renderErrors = function(errors) { if (this.dom.validationErrors) { this.dom.validationErrors.parentNode.removeChild(this.dom.validationErrors); this.dom.validationErrors = null; - this.dom.additinalErrorsIndication.style.display = 'none'; + this.dom.additionalErrorsIndication.style.display = 'none'; this.content.style.marginBottom = ''; this.content.style.paddingBottom = ''; @@ -933,12 +940,12 @@ textmode._renderErrors = function(errors) { this.dom.validationErrors = validationErrors; this.dom.validationErrorsContainer.appendChild(validationErrors); - this.dom.additinalErrorsIndication.title = errors.length + " errors total"; + this.dom.additionalErrorsIndication.title = errors.length + " errors total"; if (this.dom.validationErrorsContainer.clientHeight < this.dom.validationErrorsContainer.scrollHeight) { - this.dom.additinalErrorsIndication.style.display = 'block'; + this.dom.additionalErrorsIndication.style.display = 'block'; this.dom.validationErrorsContainer.onscroll = function () { - me.dom.additinalErrorsIndication.style.display = + me.dom.additionalErrorsIndication.style.display = (me.dom.validationErrorsContainer.clientHeight > 0 && me.dom.validationErrorsContainer.scrollTop === 0) ? 'block' : 'none'; } } else { @@ -1027,13 +1034,10 @@ textmode.getTextSelection = function () { }; /** - * Callback registraion for selection change + * Callback registration for selection change * @param {selectionCallback} callback * * @callback selectionCallback - * @param {{row:Number, column:Number}} startPos selection start position - * @param {{row:Number, column:Number}} endPos selected end position - * @param {String} text selected text */ textmode.onTextSelectionChange = function (callback) { if (typeof callback === 'function') { diff --git a/src/js/treemode.js b/src/js/treemode.js index 07440aa..f6f29b0 100644 --- a/src/js/treemode.js +++ b/src/js/treemode.js @@ -129,8 +129,6 @@ treemode.destroy = function () { * @private */ treemode._setOptions = function (options) { - var editor = this; - this.options = { search: true, history: true, @@ -140,6 +138,7 @@ treemode._setOptions = function (options) { schemaRefs: null, autocomplete: null, navigationBar : true, + mainMenuBar: true, onSelectionChange: null, colorPicker: true, onColorPicker: function (parent, color, onChange) { @@ -149,11 +148,11 @@ treemode._setOptions = function (options) { color: color, popup: 'bottom', onDone: function (color) { - var alpha = color.rgba[3] + var alpha = color.rgba[3]; var hex = (alpha === 1) ? color.hex.substr(0, 7) // return #RRGGBB - : color.hex // return #RRGGBBAA - onChange(hex) + : color.hex; // return #RRGGBBAA + onChange(hex); } }).show(); } @@ -942,6 +941,9 @@ treemode._createFrame = function () { this.frame.className = 'jsoneditor jsoneditor-mode-' + this.options.mode; this.container.appendChild(this.frame); + this.contentOuter = document.createElement('div'); + this.contentOuter.className = 'jsoneditor-outer'; + // create one global event listener to handle all events from all nodes var editor = this; function onEvent(event) { @@ -980,105 +982,109 @@ treemode._createFrame = function () { this.frame.onfocusin = onEvent; // for IE this.frame.onfocusout = onEvent; // for IE - // create menu - this.menu = document.createElement('div'); - this.menu.className = 'jsoneditor-menu'; - this.frame.appendChild(this.menu); + if (this.options.mainMenuBar) { + util.addClassName(this.contentOuter, 'has-main-menu-bar'); - // create expand all button - var expandAll = document.createElement('button'); - expandAll.type = 'button'; - expandAll.className = 'jsoneditor-expand-all'; - expandAll.title = translate('expandAll'); - expandAll.onclick = function () { - editor.expandAll(); - }; - this.menu.appendChild(expandAll); + // create menu + this.menu = document.createElement('div'); + this.menu.className = 'jsoneditor-menu'; + this.frame.appendChild(this.menu); - // create collapse all button - var collapseAll = document.createElement('button'); - collapseAll.type = 'button'; - collapseAll.title = translate('collapseAll'); - collapseAll.className = 'jsoneditor-collapse-all'; - collapseAll.onclick = function () { - editor.collapseAll(); - }; - this.menu.appendChild(collapseAll); - - // create sort button - if (this.options.enableSort) { - var sort = document.createElement('button'); - sort.type = 'button'; - sort.className = 'jsoneditor-sort'; - sort.title = translate('sortTitleShort'); - sort.onclick = function () { - var anchor = editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR; - showSortModal(editor.node, anchor) + // create expand all button + var expandAll = document.createElement('button'); + expandAll.type = 'button'; + expandAll.className = 'jsoneditor-expand-all'; + expandAll.title = translate('expandAll'); + expandAll.onclick = function () { + editor.expandAll(); }; - this.menu.appendChild(sort); + this.menu.appendChild(expandAll); + + // create collapse all button + var collapseAll = document.createElement('button'); + collapseAll.type = 'button'; + collapseAll.title = translate('collapseAll'); + collapseAll.className = 'jsoneditor-collapse-all'; + collapseAll.onclick = function () { + editor.collapseAll(); + }; + this.menu.appendChild(collapseAll); + + // create sort button + if (this.options.enableSort) { + var sort = document.createElement('button'); + sort.type = 'button'; + sort.className = 'jsoneditor-sort'; + sort.title = translate('sortTitleShort'); + sort.onclick = function () { + var anchor = editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR; + showSortModal(editor.node, anchor) + }; + this.menu.appendChild(sort); + } + + // create transform button + if (this.options.enableTransform) { + var transform = document.createElement('button'); + transform.type = 'button'; + transform.title = translate('transformTitleShort'); + transform.className = 'jsoneditor-transform'; + transform.onclick = function () { + var anchor = editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR; + showTransformModal(editor.node, anchor) + }; + this.menu.appendChild(transform); + } + + // create undo/redo buttons + if (this.history) { + // create undo button + var undo = document.createElement('button'); + undo.type = 'button'; + undo.className = 'jsoneditor-undo jsoneditor-separator'; + undo.title = translate('undo'); + undo.onclick = function () { + editor._onUndo(); + }; + this.menu.appendChild(undo); + this.dom.undo = undo; + + // create redo button + var redo = document.createElement('button'); + redo.type = 'button'; + redo.className = 'jsoneditor-redo'; + redo.title = translate('redo'); + redo.onclick = function () { + editor._onRedo(); + }; + this.menu.appendChild(redo); + this.dom.redo = redo; + + // register handler for onchange of history + this.history.onChange = function () { + undo.disabled = !editor.history.canUndo(); + redo.disabled = !editor.history.canRedo(); + }; + this.history.onChange(); + } + + // create mode box + if (this.options && this.options.modes && this.options.modes.length) { + var me = this; + this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) { + // switch mode and restore focus + me.setMode(mode); + me.modeSwitcher.focus(); + }); + } + + // create search box + if (this.options.search) { + this.searchBox = new SearchBox(this, this.menu); + } } - // create transform button - if (this.options.enableTransform) { - var transform = document.createElement('button'); - transform.type = 'button'; - transform.title = translate('transformTitleShort'); - transform.className = 'jsoneditor-transform'; - transform.onclick = function () { - var anchor = editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR; - showTransformModal(editor.node, anchor) - }; - this.menu.appendChild(transform); - } - - // create undo/redo buttons - if (this.history) { - // create undo button - var undo = document.createElement('button'); - undo.type = 'button'; - undo.className = 'jsoneditor-undo jsoneditor-separator'; - undo.title = translate('undo'); - undo.onclick = function () { - editor._onUndo(); - }; - this.menu.appendChild(undo); - this.dom.undo = undo; - - // create redo button - var redo = document.createElement('button'); - redo.type = 'button'; - redo.className = 'jsoneditor-redo'; - redo.title = translate('redo'); - redo.onclick = function () { - editor._onRedo(); - }; - this.menu.appendChild(redo); - this.dom.redo = redo; - - // register handler for onchange of history - this.history.onChange = function () { - undo.disabled = !editor.history.canUndo(); - redo.disabled = !editor.history.canRedo(); - }; - this.history.onChange(); - } - - // create mode box - if (this.options && this.options.modes && this.options.modes.length) { - var me = this; - this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) { - // switch mode and restore focus - me.setMode(mode); - me.modeSwitcher.focus(); - }); - } - - // create search box - if (this.options.search) { - this.searchBox = new SearchBox(this, this.menu); - } - - if(this.options.navigationBar) { + if (this.options.navigationBar) { // create second menu row for treepath this.navBar = document.createElement('div'); this.navBar.className = 'jsoneditor-navigation-bar nav-bar-empty'; @@ -1209,7 +1215,7 @@ treemode._updateTreePath = function (pathNodes) { name: getName(node), node: node, children: [] - } + }; if (node.childs && node.childs.length) { node.childs.forEach(function (childNode) { pathObj.children.push({ @@ -1373,10 +1379,9 @@ treemode._onMultiSelect = function (event) { /** * End of multiselect nodes by dragging - * @param event * @private */ -treemode._onMultiSelectEnd = function (event) { +treemode._onMultiSelectEnd = function () { // set focus to the context menu button of the first node if (this.multiselection.nodes[0]) { this.multiselection.nodes[0].dom.menu.focus(); @@ -1600,16 +1605,13 @@ treemode._onKeyDown = function (event) { * @private */ treemode._createTable = function () { - var contentOuter = document.createElement('div'); - contentOuter.className = 'jsoneditor-outer'; - if(this.options.navigationBar) { - util.addClassName(contentOuter, 'has-nav-bar'); + if (this.options.navigationBar) { + util.addClassName(this.contentOuter, 'has-nav-bar'); } - this.contentOuter = contentOuter; this.scrollableContent = document.createElement('div'); this.scrollableContent.className = 'jsoneditor-tree'; - contentOuter.appendChild(this.scrollableContent); + this.contentOuter.appendChild(this.scrollableContent); // the jsoneditor-tree-inner div with bottom padding is here to // keep space for the action menu dropdown. It's created as a @@ -1643,7 +1645,7 @@ treemode._createTable = function () { this.tbody = document.createElement('tbody'); this.table.appendChild(this.tbody); - this.frame.appendChild(contentOuter); + this.frame.appendChild(this.contentOuter); }; /** @@ -1707,12 +1709,10 @@ treemode.getSelection = function () { }; /** - * Callback registraion for selection change + * Callback registration for selection change * @param {selectionCallback} callback * * @callback selectionCallback - * @param {SerializableNode=} start - * @param {SerializableNode=} end */ treemode.onSelectionChange = function (callback) { if (typeof callback === 'function') { @@ -1731,7 +1731,7 @@ treemode.onSelectionChange = function (callback) { treemode.setSelection = function (start, end) { // check for old usage if (start && start.dom && start.range) { - console.warn('setSelection/getSelection usage for text selection is depracated and should not be used, see documantaion for supported selection options'); + console.warn('setSelection/getSelection usage for text selection is deprecated and should not be used, see documentation for supported selection options'); this.setDomSelection(start); } @@ -1747,7 +1747,7 @@ treemode.setSelection = function (start, end) { * Returns a set of Nodes according to a range of selection * @param {{path: Array.}} start object contains the path for range start * @param {{path: Array.}=} end object contains the path for range end - * @return {Array.} Node intances on the given range + * @return {Array.} Node instances on the given range * @private */ treemode._getNodeInstancesByRange = function (start, end) {