Merge pull request #457 from meirotstein/path_and_count
Add selection count, cursor location and tree path
This commit is contained in:
commit
ea939d48cc
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -180,6 +180,13 @@ 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 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.
|
- Can return a `Promise` resolving one of the return types above to support asynchronously retrieving a list with options.
|
||||||
|
|
||||||
|
- `{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'.
|
||||||
|
|
||||||
|
- `{boolean} statusBar`
|
||||||
|
|
||||||
|
Adds status bar to the buttom of the editor - the status bar shows the cursor position (currently only for 'code' `mode`) and a count of the selected charcters. True by default. Only applicable when `mode` is 'code' or 'text'.
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,10 @@ gulp.task('bundle-css', ['mkdir'], function () {
|
||||||
'src/css/contextmenu.css',
|
'src/css/contextmenu.css',
|
||||||
'src/css/menu.css',
|
'src/css/menu.css',
|
||||||
'src/css/searchbox.css',
|
'src/css/searchbox.css',
|
||||||
'src/css/autocomplete.css'
|
'src/css/autocomplete.css',
|
||||||
|
'src/css/treepath.css',
|
||||||
|
'src/css/statusbar.css',
|
||||||
|
'src/css/navigationbar.css'
|
||||||
])
|
])
|
||||||
.pipe(concatCss(NAME + '.css'))
|
.pipe(concatCss(NAME + '.css'))
|
||||||
.pipe(gulp.dest(DIST))
|
.pipe(gulp.dest(DIST))
|
||||||
|
|
|
@ -210,6 +210,16 @@ div.jsoneditor-outer {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-outer.has-nav-bar {
|
||||||
|
margin: -61px 0 0 0;
|
||||||
|
padding: 61px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-outer.has-status-bar {
|
||||||
|
margin: -35px 0 -16px 0;
|
||||||
|
padding: 35px 0 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
textarea.jsoneditor-text,
|
textarea.jsoneditor-text,
|
||||||
.ace-jsoneditor {
|
.ace-jsoneditor {
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
div.jsoneditor-navigation-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 26px;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 0;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #808080;
|
||||||
|
background-color: #dcdcdc;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-navigation-bar:before {
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: white;
|
||||||
|
opacity: 0.8;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
margin-top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-navigation-bar.nav-bar-empty:after {
|
||||||
|
content: 'Select a node ...';
|
||||||
|
color: rgba(104, 104, 91, 0.56);
|
||||||
|
position: absolute;
|
||||||
|
margin-left: 6px;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
div.jsoneditor-statusbar {
|
||||||
|
line-height: 17px;
|
||||||
|
height: 17px;
|
||||||
|
color: #808080;
|
||||||
|
background-color: #dcdcdc;
|
||||||
|
margin-top: -17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-statusbar > .jsoneditor-curserinfo-label {
|
||||||
|
margin: 0 2px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-statusbar > .jsoneditor-curserinfo-val {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-statusbar > .jsoneditor-curserinfo-count {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-statusbar > span {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
div.jsoneditor-treepath {
|
||||||
|
padding: 3px 0 2px 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-treepath div.jsoneditor-contextmenu-root {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-treepath span.jsoneditor-treepath-element{
|
||||||
|
margin: 1px;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-treepath span.jsoneditor-treepath-seperator {
|
||||||
|
margin: 2px;
|
||||||
|
font-size: 9pt;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.jsoneditor-treepath span.jsoneditor-treepath-element:hover, div.jsoneditor-treepath span.jsoneditor-treepath-seperator:hover {
|
||||||
|
cursor:pointer;
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
|
@ -211,8 +211,12 @@ ContextMenu.prototype.show = function (anchor, contentWindow) {
|
||||||
|
|
||||||
// determine whether to display the menu below or above the anchor
|
// determine whether to display the menu below or above the anchor
|
||||||
var showBelow = true;
|
var showBelow = true;
|
||||||
|
var parent = anchor.parentNode;
|
||||||
|
var anchorRect = anchor.getBoundingClientRect();
|
||||||
|
var parentRect = parent.getBoundingClientRect()
|
||||||
|
|
||||||
if (contentWindow) {
|
if (contentWindow) {
|
||||||
var anchorRect = anchor.getBoundingClientRect();
|
|
||||||
var contentRect = contentWindow.getBoundingClientRect();
|
var contentRect = contentWindow.getBoundingClientRect();
|
||||||
|
|
||||||
if (anchorRect.bottom + this.maxHeight < contentRect.bottom) {
|
if (anchorRect.bottom + this.maxHeight < contentRect.bottom) {
|
||||||
|
@ -227,18 +231,21 @@ ContextMenu.prototype.show = function (anchor, contentWindow) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var leftGap = anchorRect.left - parentRect.left;
|
||||||
|
var topGap = anchorRect.top - parentRect.top;
|
||||||
|
|
||||||
// position the menu
|
// position the menu
|
||||||
if (showBelow) {
|
if (showBelow) {
|
||||||
// display the menu below the anchor
|
// display the menu below the anchor
|
||||||
var anchorHeight = anchor.offsetHeight;
|
var anchorHeight = anchor.offsetHeight;
|
||||||
this.dom.menu.style.left = '0px';
|
this.dom.menu.style.left = leftGap + 'px';
|
||||||
this.dom.menu.style.top = anchorHeight + 'px';
|
this.dom.menu.style.top = topGap + 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 = '0px';
|
this.dom.menu.style.left = leftGap + 'px';
|
||||||
this.dom.menu.style.top = '';
|
this.dom.menu.style.top = topGap + 'px';
|
||||||
this.dom.menu.style.bottom = '0px';
|
this.dom.menu.style.bottom = '0px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +253,6 @@ ContextMenu.prototype.show = function (anchor, contentWindow) {
|
||||||
this.rootNode = getRootNode(anchor);
|
this.rootNode = getRootNode(anchor);
|
||||||
|
|
||||||
// attach the menu to the parent of the anchor
|
// attach the menu to the parent of the anchor
|
||||||
var parent = anchor.parentNode;
|
|
||||||
parent.insertBefore(this.dom.root, parent.firstChild);
|
parent.insertBefore(this.dom.root, parent.firstChild);
|
||||||
|
|
||||||
// create and attach event listeners
|
// create and attach event listeners
|
||||||
|
|
|
@ -82,7 +82,8 @@ function JSONEditor (container, options, json) {
|
||||||
'ajv', 'schema', 'schemaRefs','templates',
|
'ajv', 'schema', 'schemaRefs','templates',
|
||||||
'ace', 'theme','autocomplete',
|
'ace', 'theme','autocomplete',
|
||||||
'onChange', 'onEditable', 'onError', 'onModeChange',
|
'onChange', 'onEditable', 'onError', 'onModeChange',
|
||||||
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'sortObjectKeys'
|
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
|
||||||
|
'sortObjectKeys', 'navigationBar', 'statusBar'
|
||||||
];
|
];
|
||||||
|
|
||||||
Object.keys(options).forEach(function (option) {
|
Object.keys(options).forEach(function (option) {
|
||||||
|
|
|
@ -539,6 +539,20 @@ Node.prototype.hideChilds = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goes through the path from the node to the root and ensures that it is expanded
|
||||||
|
*/
|
||||||
|
Node.prototype.expandTo = function() {
|
||||||
|
var currentNode = this.parent;
|
||||||
|
while (currentNode) {
|
||||||
|
if (!currentNode.expanded) {
|
||||||
|
currentNode.expand();
|
||||||
|
}
|
||||||
|
currentNode = currentNode.parent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new child to the node.
|
* Add a new child to the node.
|
||||||
* Only applicable when Node value is of type array or object
|
* Only applicable when Node value is of type array or object
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var ContextMenu = require('./ContextMenu');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a component that visualize path selection in tree based editors
|
||||||
|
* @param {HTMLElement} container
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function TreePath(container) {
|
||||||
|
if (container) {
|
||||||
|
this.path = document.createElement('div');
|
||||||
|
this.path.className = 'jsoneditor-treepath';
|
||||||
|
container.appendChild(this.path);
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset component to initial status
|
||||||
|
*/
|
||||||
|
TreePath.prototype.reset = function () {
|
||||||
|
this.path.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the component UI according to a given path objects
|
||||||
|
* @param {Array<name: String, childs: Array>} pathObjs a list of path objects
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
TreePath.prototype.setPath = function (pathObjs) {
|
||||||
|
var me = this;
|
||||||
|
this.reset();
|
||||||
|
if (pathObjs && pathObjs.length) {
|
||||||
|
pathObjs.forEach(function (pathObj, idx) {
|
||||||
|
var pathEl = document.createElement('span');
|
||||||
|
var sepEl;
|
||||||
|
pathEl.className = 'jsoneditor-treepath-element';
|
||||||
|
pathEl.innerText = pathObj.name;
|
||||||
|
pathEl.onclick = _onSegmentClick.bind(me, pathObj);
|
||||||
|
|
||||||
|
me.path.appendChild(pathEl);
|
||||||
|
|
||||||
|
if (pathObj.children.length) {
|
||||||
|
sepEl = document.createElement('span');
|
||||||
|
sepEl.className = 'jsoneditor-treepath-seperator';
|
||||||
|
sepEl.innerHTML = '►';
|
||||||
|
|
||||||
|
sepEl.onclick = function () {
|
||||||
|
var items = [];
|
||||||
|
pathObj.children.forEach(function (child) {
|
||||||
|
items.push({
|
||||||
|
'text': child.name,
|
||||||
|
'className': 'jsoneditor-type-modes' + (pathObjs[idx + 1] + 1 && pathObjs[idx + 1].name === child.name ? ' jsoneditor-selected' : ''),
|
||||||
|
'click': _onContextMenuItemClick.bind(me, pathObj, child.name)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var menu = new ContextMenu(items);
|
||||||
|
menu.show(sepEl);
|
||||||
|
};
|
||||||
|
|
||||||
|
me.path.appendChild(sepEl, me.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(idx === pathObjs.length - 1) {
|
||||||
|
var leftRectPos = (sepEl || pathEl).getBoundingClientRect().left;
|
||||||
|
if(me.path.offsetWidth < leftRectPos) {
|
||||||
|
me.path.scrollLeft = leftRectPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onSegmentClick(pathObj) {
|
||||||
|
if (this.selectionCallback) {
|
||||||
|
this.selectionCallback(pathObj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function _onContextMenuItemClick(pathObj, selection) {
|
||||||
|
if (this.contextMenuCallback) {
|
||||||
|
this.contextMenuCallback(pathObj, selection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set a callback function for selection of path section
|
||||||
|
* @param {Function} callback function to invoke when section is selected
|
||||||
|
*/
|
||||||
|
TreePath.prototype.onSectionSelected = function (callback) {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
this.selectionCallback = callback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set a callback function for selection of path section
|
||||||
|
* @param {Function} callback function to invoke when section is selected
|
||||||
|
*/
|
||||||
|
TreePath.prototype.onContextMenuItemSelected = function (callback) {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
this.contextMenuCallback = callback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = TreePath;
|
|
@ -36,6 +36,11 @@ var DEFAULT_THEME = 'ace/theme/jsoneditor';
|
||||||
textmode.create = function (container, options) {
|
textmode.create = function (container, options) {
|
||||||
// read options
|
// read options
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
|
if(typeof options.statusBar === 'undefined') {
|
||||||
|
options.statusBar = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
// indentation
|
// indentation
|
||||||
|
@ -200,6 +205,7 @@ textmode.create = function (container, options) {
|
||||||
|
|
||||||
// register onchange event
|
// register onchange event
|
||||||
aceEditor.on('change', this._onChange.bind(this));
|
aceEditor.on('change', this._onChange.bind(this));
|
||||||
|
aceEditor.on('changeSelection', this._onSelect.bind(this));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// load a plain text textarea
|
// load a plain text textarea
|
||||||
|
@ -218,9 +224,66 @@ textmode.create = function (container, options) {
|
||||||
// oninput is undefined. For IE8-
|
// oninput is undefined. For IE8-
|
||||||
this.textarea.onchange = this._onChange.bind(this);
|
this.textarea.onchange = this._onChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea.onselect = this._onSelect.bind(this);
|
||||||
|
textarea.onmousedown = this._onMouseDown.bind(this);
|
||||||
|
textarea.onblur = this._onBlur.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setSchema(this.options.schema, this.options.schemaRefs);
|
if (options.statusBar) {
|
||||||
|
|
||||||
|
util.addClassName(this.content, 'has-status-bar');
|
||||||
|
|
||||||
|
this.curserInfoElements = {};
|
||||||
|
var statusBar = document.createElement('div');
|
||||||
|
statusBar.className = 'jsoneditor-statusbar';
|
||||||
|
this.frame.appendChild(statusBar);
|
||||||
|
|
||||||
|
if (this.mode == 'code') {
|
||||||
|
var lnLabel = document.createElement('span');
|
||||||
|
lnLabel.className = 'jsoneditor-curserinfo-label';
|
||||||
|
lnLabel.innerText = 'Ln:';
|
||||||
|
|
||||||
|
var lnVal = document.createElement('span');
|
||||||
|
lnVal.className = 'jsoneditor-curserinfo-val';
|
||||||
|
lnVal.innerText = 0;
|
||||||
|
|
||||||
|
statusBar.appendChild(lnLabel);
|
||||||
|
statusBar.appendChild(lnVal);
|
||||||
|
|
||||||
|
var colLabel = document.createElement('span');
|
||||||
|
colLabel.className = 'jsoneditor-curserinfo-label';
|
||||||
|
colLabel.innerText = 'Col:';
|
||||||
|
|
||||||
|
var colVal = document.createElement('span');
|
||||||
|
colVal.className = 'jsoneditor-curserinfo-val';
|
||||||
|
colVal.innerText = 0;
|
||||||
|
|
||||||
|
statusBar.appendChild(colLabel);
|
||||||
|
statusBar.appendChild(colVal);
|
||||||
|
|
||||||
|
this.curserInfoElements.colVal = colVal;
|
||||||
|
this.curserInfoElements.lnVal = lnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
var countLabel = document.createElement('span');
|
||||||
|
countLabel.className = 'jsoneditor-curserinfo-label';
|
||||||
|
countLabel.innerText = 'selected';
|
||||||
|
countLabel.style.display = 'none';
|
||||||
|
|
||||||
|
var countVal = document.createElement('span');
|
||||||
|
countVal.className = 'jsoneditor-curserinfo-count';
|
||||||
|
countVal.innerText = 0;
|
||||||
|
countVal.style.display = 'none';
|
||||||
|
|
||||||
|
this.curserInfoElements.countLabel = countLabel;
|
||||||
|
this.curserInfoElements.countVal = countVal;
|
||||||
|
|
||||||
|
statusBar.appendChild(countVal);
|
||||||
|
statusBar.appendChild(countLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSchema(this.options.schema, this.options.schemaRefs);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,6 +307,28 @@ textmode._onChange = function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle text selection
|
||||||
|
* Calculates the cursor position and selection range and updates menu
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
textmode._onSelect = function () {
|
||||||
|
if(this.options.statusBar) {
|
||||||
|
if (this.textarea) {
|
||||||
|
var selectionRange = util.getInputSelection(this.textarea);
|
||||||
|
if (selectionRange.start !== selectionRange.end) {
|
||||||
|
this._setSelectionCountDisplay(Math.abs(selectionRange.end - selectionRange.start));
|
||||||
|
}
|
||||||
|
} else if (this.aceEditor) {
|
||||||
|
var curserPos = this.aceEditor.getCursorPosition();
|
||||||
|
var selectedText = this.aceEditor.getSelectedText();
|
||||||
|
this.curserInfoElements.lnVal.innerText = curserPos.row + 1;
|
||||||
|
this.curserInfoElements.colVal.innerText = curserPos.column + 1;
|
||||||
|
this._setSelectionCountDisplay(selectedText.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for keydown. Handles shortcut keys
|
* Event handler for keydown. Handles shortcut keys
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
|
@ -269,6 +354,39 @@ textmode._onKeyDown = function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._setSelectionCountDisplay();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for mousedown.
|
||||||
|
* @param {Event} event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
textmode._onMouseDown = function (event) {
|
||||||
|
this._setSelectionCountDisplay();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for blur.
|
||||||
|
* @param {Event} event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
textmode._onBlur = function (event) {
|
||||||
|
this._setSelectionCountDisplay();
|
||||||
|
};
|
||||||
|
|
||||||
|
textmode._setSelectionCountDisplay = function (value) {
|
||||||
|
if (this.options.statusBar) {
|
||||||
|
if (value && this.curserInfoElements.countVal) {
|
||||||
|
this.curserInfoElements.countVal.innerText = value;
|
||||||
|
this.curserInfoElements.countVal.style.display = 'inline';
|
||||||
|
this.curserInfoElements.countLabel.style.display = 'inline';
|
||||||
|
} else {
|
||||||
|
this.curserInfoElements.countVal.style.display = 'none';
|
||||||
|
this.curserInfoElements.countLabel.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -405,7 +523,6 @@ textmode.setText = function(jsonText) {
|
||||||
|
|
||||||
this.options.onChange = originalOnChange;
|
this.options.onChange = originalOnChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate JSON schema
|
// validate JSON schema
|
||||||
this.validate();
|
this.validate();
|
||||||
};
|
};
|
||||||
|
@ -445,7 +562,7 @@ textmode.validate = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
// limit the number of displayed errors
|
// limit the number of displayed errors
|
||||||
var limit = errors.length > MAX_ERRORS;
|
var limit = errors.length > MAX_ERRORS;
|
||||||
if (limit) {
|
if (limit) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ var Highlighter = require('./Highlighter');
|
||||||
var History = require('./History');
|
var History = require('./History');
|
||||||
var SearchBox = require('./SearchBox');
|
var SearchBox = require('./SearchBox');
|
||||||
var ContextMenu = require('./ContextMenu');
|
var ContextMenu = require('./ContextMenu');
|
||||||
|
var TreePath = require('./TreePath');
|
||||||
var Node = require('./Node');
|
var Node = require('./Node');
|
||||||
var ModeSwitcher = require('./ModeSwitcher');
|
var ModeSwitcher = require('./ModeSwitcher');
|
||||||
var util = require('./util');
|
var util = require('./util');
|
||||||
|
@ -113,7 +114,8 @@ treemode._setOptions = function (options) {
|
||||||
name: undefined, // field name of root node
|
name: undefined, // field name of root node
|
||||||
schema: null,
|
schema: null,
|
||||||
schemaRefs: null,
|
schemaRefs: null,
|
||||||
autocomplete: null
|
autocomplete: null,
|
||||||
|
navigationBar : true
|
||||||
};
|
};
|
||||||
|
|
||||||
// copy all options
|
// copy all options
|
||||||
|
@ -757,6 +759,17 @@ treemode._createFrame = function () {
|
||||||
if (this.options.search) {
|
if (this.options.search) {
|
||||||
this.searchBox = new SearchBox(this, this.menu);
|
this.searchBox = new SearchBox(this, this.menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.options.navigationBar) {
|
||||||
|
// create second menu row for treepath
|
||||||
|
this.navBar = document.createElement('div');
|
||||||
|
this.navBar.className = 'jsoneditor-navigation-bar nav-bar-empty';
|
||||||
|
this.frame.appendChild(this.navBar);
|
||||||
|
|
||||||
|
this.treePath = new TreePath(this.navBar);
|
||||||
|
this.treePath.onSectionSelected(this._onTreePathSectionSelected.bind(this));
|
||||||
|
this.treePath.onContextMenuItemSelected(this._onTreePathMenuItemSelected.bind(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -810,6 +823,10 @@ treemode._onEvent = function (event) {
|
||||||
|
|
||||||
var node = Node.getNodeFromTarget(event.target);
|
var node = Node.getNodeFromTarget(event.target);
|
||||||
|
|
||||||
|
if (this.options && this.options.navigationBar && node && (event.type == 'keydown' || event.type == 'mousedown')) {
|
||||||
|
this._updateTreePath(node.getNodePath());
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -850,6 +867,73 @@ treemode._onEvent = function (event) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update TreePath components
|
||||||
|
* @param {Array<Node>} pathNodes list of nodes in path from root to selection
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
treemode._updateTreePath = function (pathNodes) {
|
||||||
|
if (pathNodes && pathNodes.length) {
|
||||||
|
util.removeClassName(this.navBar, 'nav-bar-empty');
|
||||||
|
|
||||||
|
var pathObjs = [];
|
||||||
|
pathNodes.forEach(function (node) {
|
||||||
|
var pathObj = {
|
||||||
|
name: getName(node),
|
||||||
|
node: node,
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
if (node.childs && node.childs.length) {
|
||||||
|
node.childs.forEach(function (childNode) {
|
||||||
|
pathObj.children.push({
|
||||||
|
name: getName(childNode),
|
||||||
|
node: childNode
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pathObjs.push(pathObj);
|
||||||
|
});
|
||||||
|
this.treePath.setPath(pathObjs);
|
||||||
|
} else {
|
||||||
|
util.addClassName(this.navBar, 'nav-bar-empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getName(node) {
|
||||||
|
return node.field || (isNaN(node.index) ? node.type : node.index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for tree path section selection - focus the selected node in the tree
|
||||||
|
* @param {Object} pathObj path object that was represents the selected section node
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
treemode._onTreePathSectionSelected = function (pathObj) {
|
||||||
|
if(pathObj && pathObj.node) {
|
||||||
|
pathObj.node.expandTo();
|
||||||
|
pathObj.node.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for tree path menu item selection - rebuild the path accrding to the new selection and focus the selected node in the tree
|
||||||
|
* @param {Object} pathObj path object that was represents the parent section node
|
||||||
|
* @param {String} selection selected section child
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
treemode._onTreePathMenuItemSelected = function (pathObj, selection) {
|
||||||
|
if(pathObj && pathObj.children.length) {
|
||||||
|
var selectionObj = pathObj.children.find(function (obj) {
|
||||||
|
return obj.name === selection;
|
||||||
|
});
|
||||||
|
if(selectionObj && selectionObj.node) {
|
||||||
|
this._updateTreePath(selectionObj.node.getNodePath());
|
||||||
|
selectionObj.node.expandTo();
|
||||||
|
selectionObj.node.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
treemode._startDragDistance = function (event) {
|
treemode._startDragDistance = function (event) {
|
||||||
this.dragDistanceEvent = {
|
this.dragDistanceEvent = {
|
||||||
initialTarget: event.target,
|
initialTarget: event.target,
|
||||||
|
@ -1162,6 +1246,9 @@ treemode._onKeyDown = function (event) {
|
||||||
treemode._createTable = function () {
|
treemode._createTable = function () {
|
||||||
var contentOuter = document.createElement('div');
|
var contentOuter = document.createElement('div');
|
||||||
contentOuter.className = 'jsoneditor-outer';
|
contentOuter.className = 'jsoneditor-outer';
|
||||||
|
if(this.options.navigationBar) {
|
||||||
|
util.addClassName(contentOuter, 'has-nav-bar');
|
||||||
|
}
|
||||||
this.contentOuter = contentOuter;
|
this.contentOuter = contentOuter;
|
||||||
|
|
||||||
this.content = document.createElement('div');
|
this.content = document.createElement('div');
|
||||||
|
|
|
@ -790,6 +790,60 @@ exports.textDiff = function textDiff(oldText, newText) {
|
||||||
return {start: start, end: newEnd};
|
return {start: start, end: newEnd};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object with the selection range or cursor position (if both have the same value)
|
||||||
|
* Support also old browsers (IE8-)
|
||||||
|
* Source: http://ourcodeworld.com/articles/read/282/how-to-get-the-current-cursor-position-and-selection-within-a-text-input-or-textarea-in-javascript
|
||||||
|
* @param {DOMElement} el A dom element of a textarea or input text.
|
||||||
|
* @return {Object} reference Object with 2 properties (start and end) with the identifier of the location of the cursor and selected text.
|
||||||
|
**/
|
||||||
|
exports.getInputSelection = function(el) {
|
||||||
|
var start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange;
|
||||||
|
|
||||||
|
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
|
||||||
|
start = el.selectionStart;
|
||||||
|
end = el.selectionEnd;
|
||||||
|
} else {
|
||||||
|
range = document.selection.createRange();
|
||||||
|
|
||||||
|
if (range && range.parentElement() == el) {
|
||||||
|
len = el.value.length;
|
||||||
|
normalizedValue = el.value.replace(/\r\n/g, "\n");
|
||||||
|
|
||||||
|
// Create a working TextRange that lives only in the input
|
||||||
|
textInputRange = el.createTextRange();
|
||||||
|
textInputRange.moveToBookmark(range.getBookmark());
|
||||||
|
|
||||||
|
// Check if the start and end of the selection are at the very end
|
||||||
|
// of the input, since moveStart/moveEnd doesn't return what we want
|
||||||
|
// in those cases
|
||||||
|
endRange = el.createTextRange();
|
||||||
|
endRange.collapse(false);
|
||||||
|
|
||||||
|
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||||||
|
start = end = len;
|
||||||
|
} else {
|
||||||
|
start = -textInputRange.moveStart("character", -len);
|
||||||
|
start += normalizedValue.slice(0, start).split("\n").length - 1;
|
||||||
|
|
||||||
|
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
|
||||||
|
end = len;
|
||||||
|
} else {
|
||||||
|
end = -textInputRange.moveEnd("character", -len);
|
||||||
|
end += normalizedValue.slice(0, end).split("\n").length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: start,
|
||||||
|
end: end
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (typeof Element !== 'undefined') {
|
if (typeof Element !== 'undefined') {
|
||||||
// Polyfill for array remove
|
// Polyfill for array remove
|
||||||
(function (arr) {
|
(function (arr) {
|
||||||
|
@ -817,4 +871,16 @@ if (!String.prototype.startsWith) {
|
||||||
position = position || 0;
|
position = position || 0;
|
||||||
return this.substr(position, searchString.length) === searchString;
|
return this.substr(position, searchString.length) === searchString;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill for Array.find
|
||||||
|
if (!Array.prototype.find) {
|
||||||
|
Array.prototype.find = function(callback) {
|
||||||
|
for (var i = 0; i < this.length; i++) {
|
||||||
|
var element = this[i];
|
||||||
|
if ( callback.call(this, element, i, this) ) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue