diff --git a/examples/25_custom_buttons.html b/examples/25_custom_buttons.html new file mode 100644 index 000000000..3a8fee904 --- /dev/null +++ b/examples/25_custom_buttons.html @@ -0,0 +1,113 @@ + + + + + + JSONEditor | Custom buttons + + + + + + + + + + +

Custom buttons

+

+ This example demonstrates how to append custom buttons to the head menu + bar. The custom buttons is available in text mode. +

+

+ This example use custom buttons to load/save local files. Powered by + FileReader.js and + FileSaver.js.
+ Only supported on modern browsers (Chrome, FireFox, IE10+, Safari 6.1+, + Opera 15+). +

+ +
+ + + + diff --git a/examples/image/load.svg b/examples/image/load.svg new file mode 100644 index 000000000..c9ea02627 --- /dev/null +++ b/examples/image/load.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/examples/image/save.svg b/examples/image/save.svg new file mode 100644 index 000000000..6dba329f8 --- /dev/null +++ b/examples/image/save.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/js/JSONEditor.js b/src/js/JSONEditor.js index b7fb38556..6b5ff4c1c 100644 --- a/src/js/JSONEditor.js +++ b/src/js/JSONEditor.js @@ -2,6 +2,7 @@ const ace = require('./ace') // may be undefined in case of minimalist bundle const VanillaPicker = require('./vanilla-picker') // may be undefined in case of minimalist bundle +const { ModeSwitcher } = require('./ModeSwitcher') const { treeModeMixins } = require('./treemode') const { textModeMixins } = require('./textmode') const { previewModeMixins } = require('./previewmode') @@ -9,6 +10,7 @@ const { clear, extend, getInnerText, getInternetExplorerVersion, parse } = requi const { tryRequireAjv } = require('./tryRequireAjv') const { showTransformModal } = require('./showTransformModal') const { showSortModal } = require('./showSortModal') +const { defaultOptions } = require('./defaultOptions') const Ajv = tryRequireAjv() @@ -187,7 +189,7 @@ JSONEditor.VALID_OPTIONS = [ 'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform', 'limitDragging', 'maxVisibleChilds', 'onValidationError', 'modalAnchor', 'popupAnchor', - 'createQuery', 'executeQuery', 'queryDescription' + 'createQuery', 'executeQuery', 'queryDescription', 'buttons', 'disableButtons' ] /** @@ -203,6 +205,7 @@ JSONEditor.prototype._create = function (container, options, json) { this.json = json || {} const mode = this.options.mode || (this.options.modes && this.options.modes[0]) || 'tree' + this._initButtons() this.setMode(mode) } @@ -471,6 +474,134 @@ JSONEditor.registerMode = mode => { } } +/** + * Create menu buttons by options.buttons + */ +JSONEditor.prototype._createButtons = function () { + if (this.options && this.options.buttons) { + this.options.buttons.forEach((buttonCustomConf) => { + // Check mode setting + let shouldCreateByModeConf = false + if (buttonCustomConf.mode === undefined) { + shouldCreateByModeConf = true + } else if ( + buttonCustomConf.mode === this.options.mode || + buttonCustomConf.mode.indexOf(this.options.mode) !== -1 + ) { + shouldCreateByModeConf = true + } + + // Check enable condition + let shouldCreateByEnableKey = false + if (buttonCustomConf.checkEnableKey) { + if (this.options[buttonCustomConf.checkEnableKey]) { + shouldCreateByEnableKey = true + } else { + shouldCreateByEnableKey = false + } + } else { + shouldCreateByEnableKey = true + } + + // Check the key in editor condition + let shouldCreateByInEditorKey = false + if (buttonCustomConf.checkInEditorKey) { + if (this[buttonCustomConf.checkInEditorKey]) { + shouldCreateByInEditorKey = true + } else { + shouldCreateByInEditorKey = false + } + } else { + shouldCreateByInEditorKey = true + } + + if ( + shouldCreateByModeConf && + shouldCreateByEnableKey && + shouldCreateByInEditorKey + ) { + // If the config is for children + if (buttonCustomConf.children) { + const createdButtonMap = {} + buttonCustomConf.children.forEach((childConf) => { + const buttonCustom = this._createButtonElement(childConf) + createdButtonMap[childConf.name] = buttonCustom + }) + if (buttonCustomConf.afterCreate) { + buttonCustomConf.afterCreate(this, createdButtonMap) + } + } else { + this._createButtonElement(buttonCustomConf) + } + } + }) + } + if (this.options && this.options.modes && this.options.modes.length) { + const 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 dom element by the button configuration + * @param {Object} buttonCustomConf The configuration of a button. + * @return {Element} The button dom element created + */ +JSONEditor.prototype._createButtonElement = function (buttonCustomConf) { + const buttonCustom = document.createElement('button') + buttonCustom.type = 'button' + buttonCustom.className = buttonCustomConf.className + buttonCustom.title = buttonCustomConf.title + buttonCustom.name = buttonCustomConf.name + this.menu.appendChild(buttonCustom) + buttonCustom.onclick = () => { + buttonCustomConf.target(this, buttonCustom) + } + if (buttonCustomConf.afterCreate) { + buttonCustomConf.afterCreate(this, buttonCustom) + } + return buttonCustom +} + +/** + * Initialize options.buttons array + */ +JSONEditor.prototype._initButtons = function () { + // Merge,filter,sort the custom buttons and built-in buttons + if (this.options) { + // Filter the default button name in the options.disableButtons + let deaultButtons = defaultOptions.buttons + let customButtons = this.options.buttons || [] + if (this.options.disableButtons) { + deaultButtons = deaultButtons.filter( + (buttonOption) => + this.options.disableButtons.indexOf(buttonOption.name) < 0 + ) + customButtons = customButtons.filter( + (buttonOption) => + this.options.disableButtons.indexOf(buttonOption.name) < 0 + ) + } + // Insert the custom buttons into deault buttons by the index of the custom button option + if (customButtons) { + customButtons.forEach((element) => { + deaultButtons.splice(element.index, 0, element) + }) + } + console.log(deaultButtons) + this.options.buttons = deaultButtons + } +} + // register tree, text, and preview modes JSONEditor.registerMode(treeModeMixins) JSONEditor.registerMode(textModeMixins) diff --git a/src/js/defaultOptions.js b/src/js/defaultOptions.js new file mode 100644 index 000000000..5cf57bb16 --- /dev/null +++ b/src/js/defaultOptions.js @@ -0,0 +1,214 @@ +import { translate } from './i18n' + +let index = 1 + +const expandAllButton = { + index: index++, + name: 'expandAll', + align: 'left', + type: 'button', + title: translate('expandAll'), + className: 'jsoneditor-expand-all', + mode: ['tree', 'view', 'form'], + target: function (editor, button) { + editor.expandAll() + } +} + +const collapseAllButton = { + index: index++, + name: 'collapseAll', + align: 'left', + type: 'button', + title: translate('collapseAll'), + className: 'jsoneditor-collapse-all', + mode: ['tree', 'view', 'form'], + target: function (editor) { + editor.collapseAll() + } +} + +const formatButton = { + index: index++, + name: 'format', + align: 'left', + type: 'button', + title: translate('formatTitle'), + className: 'jsoneditor-format', + mode: ['code', 'text'], + target: function (editor) { + try { + editor.format() + editor._onChange() + } catch (err) { + editor._onError(err) + } + } +} + +const compactButton = { + index: index++, + name: 'compact', + align: 'left', + type: 'button', + title: translate('compactTitle'), + className: 'jsoneditor-compact', + mode: ['code', 'text'], + target: function (editor) { + try { + editor.compact() + editor._onChange() + } catch (err) { + editor._onError(err) + } + } +} + +const sortButton = { + index: index++, + name: 'sort', + align: 'left', + type: 'button', + title: translate('sortTitleShort'), + className: 'jsoneditor-sort', + mode: ['code', 'text', 'tree', 'view', 'form'], + target: function (editor) { + if (['code', 'text'].indexOf(editor.options.mode) !== -1) { + editor._showSortModal() + } else if (['tree', 'view', 'form'].indexOf(editor.options.mode) !== -1) { + editor.node.showSortModal() + } else { + console.warn('Sort unsupport for mode ' + editor.options.mode) + } + }, + checkEnableKey: 'enableSort' +} + +const transformButton = { + index: index++, + name: 'transform', + align: 'left', + type: 'button', + title: translate('transformTitleShort'), + className: 'jsoneditor-transform', + mode: ['code', 'text', 'tree', 'view', 'form'], + target: function (editor) { + if (['code', 'text'].indexOf(editor.options.mode) !== -1) { + editor._showTransformModal() + } else if (['tree', 'view', 'form'].indexOf(editor.options.mode) !== -1) { + editor.node.showTransformModal() + } else { + console.warn('Transform unsupport for mode ' + editor.options.mode) + } + }, + checkEnableKey: 'enableTransform' +} + +const repairButton = { + index: index++, + name: 'repair', + align: 'left', + type: 'button', + title: translate('repairTitle'), + className: 'jsoneditor-repair', + mode: ['code', 'text'], + target: function (editor) { + try { + editor.repair() + editor._onChange() + } catch (err) { + editor._onError(err) + } + } +} + +const undoButtonForCodeMode = { + index: index++, + name: 'undo', + align: 'left', + type: 'button', + title: translate('undo'), + className: 'jsoneditor-undo jsoneditor-separator', + mode: ['code'], + target: function (editor) { + editor.aceEditor.getSession().getUndoManager().undo() + }, + afterCreate: function (editor, buttonElement) { + editor.dom.undo = buttonElement + } +} + +const redoButtonForCodeMode = { + index: index++, + name: 'redo', + align: 'left', + type: 'button', + title: translate('redo'), + className: 'jsoneditor-redo', + mode: ['code'], + target: function (editor) { + editor.aceEditor.getSession().getUndoManager().redo() + }, + afterCreate: function (editor, buttonElement) { + editor.dom.redo = buttonElement + } +} + +const undoRedoButtonPairForTreeMode = { + index: index++, + name: 'undoRedoPair', + checkInEditorKey: 'history', + mode: ['tree', 'view', 'form'], + afterCreate: function (editor, createdButtonMap) { + editor.history.onChange = () => { + createdButtonMap.undo.disabled = !editor.history.canUndo() + createdButtonMap.redo.disabled = !editor.history.canRedo() + } + editor.history.onChange() + }, + children: [ + { + name: 'undo', + align: 'left', + type: 'button', + title: translate('undo'), + className: 'jsoneditor-undo jsoneditor-separator', + target: function (editor) { + editor._onUndo() + }, + afterCreate: function (editor, buttonElement) { + editor.dom.undo = buttonElement + } + }, + { + name: 'redo', + align: 'left', + type: 'button', + title: translate('redo'), + className: 'jsoneditor-redo', + target: function (editor) { + editor._onRedo() + }, + afterCreate: function (editor, buttonElement) { + editor.dom.redo = buttonElement + } + } + ] +} + +const _buttons = [ + formatButton, + compactButton, + sortButton, + transformButton, + repairButton, + undoButtonForCodeMode, + redoButtonForCodeMode, + expandAllButton, + collapseAllButton, + undoRedoButtonPairForTreeMode +] + +export const defaultOptions = { + buttons: _buttons +} diff --git a/src/js/textmode.js b/src/js/textmode.js index c77875c8f..f269c1301 100644 --- a/src/js/textmode.js +++ b/src/js/textmode.js @@ -5,9 +5,8 @@ import ace from './ace' import { DEFAULT_MODAL_ANCHOR } from './constants' import { ErrorTable } from './ErrorTable' import { FocusTracker } from './FocusTracker' -import { setLanguage, setLanguages, translate } from './i18n' +import { setLanguage, setLanguages } from './i18n' import { createQuery, executeQuery } from './jmespathQuery' -import { ModeSwitcher } from './ModeSwitcher' import { showSortModal } from './showSortModal' import { showTransformModal } from './showTransformModal' import { tryRequireThemeJsonEditor } from './tryRequireThemeJsonEditor' @@ -131,108 +130,8 @@ textmode.create = function (container, options = {}) { this.menu.className = 'jsoneditor-menu' this.frame.appendChild(this.menu) - // create format button - const buttonFormat = document.createElement('button') - buttonFormat.type = 'button' - buttonFormat.className = 'jsoneditor-format' - buttonFormat.title = translate('formatTitle') - this.menu.appendChild(buttonFormat) - buttonFormat.onclick = () => { - try { - me.format() - me._onChange() - } catch (err) { - me._onError(err) - } - } - - // create compact button - const buttonCompact = document.createElement('button') - buttonCompact.type = 'button' - buttonCompact.className = 'jsoneditor-compact' - buttonCompact.title = translate('compactTitle') - this.menu.appendChild(buttonCompact) - buttonCompact.onclick = () => { - try { - me.compact() - me._onChange() - } catch (err) { - me._onError(err) - } - } - - // create sort button - if (this.options.enableSort) { - const sort = document.createElement('button') - sort.type = 'button' - sort.className = 'jsoneditor-sort' - sort.title = translate('sortTitleShort') - sort.onclick = () => { - me._showSortModal() - } - this.menu.appendChild(sort) - } - - // create transform button - if (this.options.enableTransform) { - const transform = document.createElement('button') - transform.type = 'button' - transform.title = translate('transformTitleShort') - transform.className = 'jsoneditor-transform' - transform.onclick = () => { - me._showTransformModal() - } - this.menu.appendChild(transform) - } - - // create repair button - const buttonRepair = document.createElement('button') - buttonRepair.type = 'button' - buttonRepair.className = 'jsoneditor-repair' - buttonRepair.title = translate('repairTitle') - this.menu.appendChild(buttonRepair) - buttonRepair.onclick = () => { - try { - me.repair() - me._onChange() - } catch (err) { - me._onError(err) - } - } - - // create undo/redo buttons - if (this.mode === 'code') { - // create undo button - const undo = document.createElement('button') - undo.type = 'button' - undo.className = 'jsoneditor-undo jsoneditor-separator' - undo.title = translate('undo') - undo.onclick = () => { - this.aceEditor.getSession().getUndoManager().undo() - } - this.menu.appendChild(undo) - this.dom.undo = undo - - // create redo button - const redo = document.createElement('button') - redo.type = 'button' - redo.className = 'jsoneditor-redo' - redo.title = translate('redo') - redo.onclick = () => { - this.aceEditor.getSession().getUndoManager().redo() - } - this.menu.appendChild(redo) - this.dom.redo = redo - } - - // 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 buttons defined in options.buttons + this._createButtons() if (this.mode === 'code') { const poweredBy = document.createElement('a') diff --git a/src/js/treemode.js b/src/js/treemode.js index 9849e4224..f6f2656de 100644 --- a/src/js/treemode.js +++ b/src/js/treemode.js @@ -6,7 +6,6 @@ import { FocusTracker } from './FocusTracker' import { Highlighter } from './Highlighter' import { setLanguage, setLanguages, translate } from './i18n' import { createQuery, executeQuery } from './jmespathQuery' -import { ModeSwitcher } from './ModeSwitcher' import { Node } from './Node' import { NodeHistory } from './NodeHistory' import { SearchBox } from './SearchBox' @@ -974,91 +973,8 @@ treemode._createFrame = function () { this.menu.className = 'jsoneditor-menu' this.frame.appendChild(this.menu) - // create expand all button - const expandAll = document.createElement('button') - expandAll.type = 'button' - expandAll.className = 'jsoneditor-expand-all' - expandAll.title = translate('expandAll') - expandAll.onclick = () => { - editor.expandAll() - } - this.menu.appendChild(expandAll) - - // create collapse all button - const collapseAll = document.createElement('button') - collapseAll.type = 'button' - collapseAll.title = translate('collapseAll') - collapseAll.className = 'jsoneditor-collapse-all' - collapseAll.onclick = () => { - editor.collapseAll() - } - this.menu.appendChild(collapseAll) - - // create sort button - if (this.options.enableSort) { - const sort = document.createElement('button') - sort.type = 'button' - sort.className = 'jsoneditor-sort' - sort.title = translate('sortTitleShort') - sort.onclick = () => { - editor.node.showSortModal() - } - this.menu.appendChild(sort) - } - - // create transform button - if (this.options.enableTransform) { - const transform = document.createElement('button') - transform.type = 'button' - transform.title = translate('transformTitleShort') - transform.className = 'jsoneditor-transform' - transform.onclick = () => { - editor.node.showTransformModal() - } - this.menu.appendChild(transform) - } - - // create undo/redo buttons - if (this.history) { - // create undo button - const undo = document.createElement('button') - undo.type = 'button' - undo.className = 'jsoneditor-undo jsoneditor-separator' - undo.title = translate('undo') - undo.onclick = () => { - editor._onUndo() - } - this.menu.appendChild(undo) - this.dom.undo = undo - - // create redo button - const redo = document.createElement('button') - redo.type = 'button' - redo.className = 'jsoneditor-redo' - redo.title = translate('redo') - redo.onclick = () => { - editor._onRedo() - } - this.menu.appendChild(redo) - this.dom.redo = redo - - // register handler for onchange of history - this.history.onChange = () => { - 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) { - const 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 buttons defined in options.buttons + this._createButtons() // create search box if (this.options.search) {