Reworked the source code to commonjs modules
This commit is contained in:
parent
abc3ac3625
commit
c18145f503
11671
jsoneditor.js
11671
jsoneditor.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,444 +1,443 @@
|
||||||
define(['./util'], function (util) {
|
var util = require('./util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A context menu
|
* A context menu
|
||||||
* @param {Object[]} items Array containing the menu structure
|
* @param {Object[]} items Array containing the menu structure
|
||||||
* TODO: describe structure
|
* TODO: describe structure
|
||||||
* @param {Object} [options] Object with options. Available options:
|
* @param {Object} [options] Object with options. Available options:
|
||||||
* {function} close Callback called when the
|
* {function} close Callback called when the
|
||||||
* context menu is being closed.
|
* context menu is being closed.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ContextMenu (items, options) {
|
function ContextMenu (items, options) {
|
||||||
this.dom = {};
|
this.dom = {};
|
||||||
|
|
||||||
var me = this;
|
var me = this;
|
||||||
var dom = this.dom;
|
var dom = this.dom;
|
||||||
this.anchor = undefined;
|
this.anchor = undefined;
|
||||||
this.items = items;
|
this.items = items;
|
||||||
this.eventListeners = {};
|
this.eventListeners = {};
|
||||||
this.selection = undefined; // holds the selection before the menu was opened
|
this.selection = undefined; // holds the selection before the menu was opened
|
||||||
this.visibleSubmenu = undefined;
|
this.visibleSubmenu = undefined;
|
||||||
this.onClose = options ? options.close : undefined;
|
this.onClose = options ? options.close : undefined;
|
||||||
|
|
||||||
// create a container element
|
// create a container element
|
||||||
var menu = document.createElement('div');
|
var menu = document.createElement('div');
|
||||||
menu.className = 'jsoneditor-contextmenu';
|
menu.className = 'jsoneditor-contextmenu';
|
||||||
dom.menu = menu;
|
dom.menu = menu;
|
||||||
|
|
||||||
// create a list to hold the menu items
|
// create a list to hold the menu items
|
||||||
var list = document.createElement('ul');
|
var list = document.createElement('ul');
|
||||||
list.className = 'menu';
|
list.className = 'menu';
|
||||||
menu.appendChild(list);
|
menu.appendChild(list);
|
||||||
dom.list = list;
|
dom.list = list;
|
||||||
dom.items = []; // list with all buttons
|
dom.items = []; // list with all buttons
|
||||||
|
|
||||||
// create a (non-visible) button to set the focus to the menu
|
// create a (non-visible) button to set the focus to the menu
|
||||||
var focusButton = document.createElement('button');
|
var focusButton = document.createElement('button');
|
||||||
dom.focusButton = focusButton;
|
dom.focusButton = focusButton;
|
||||||
var li = document.createElement('li');
|
var li = document.createElement('li');
|
||||||
li.style.overflow = 'hidden';
|
li.style.overflow = 'hidden';
|
||||||
li.style.height = '0';
|
li.style.height = '0';
|
||||||
li.appendChild(focusButton);
|
li.appendChild(focusButton);
|
||||||
list.appendChild(li);
|
list.appendChild(li);
|
||||||
|
|
||||||
function createMenuItems (list, domItems, items) {
|
function createMenuItems (list, domItems, items) {
|
||||||
items.forEach(function (item) {
|
items.forEach(function (item) {
|
||||||
if (item.type == 'separator') {
|
if (item.type == 'separator') {
|
||||||
// create a separator
|
// create a separator
|
||||||
var separator = document.createElement('div');
|
var separator = document.createElement('div');
|
||||||
separator.className = 'separator';
|
separator.className = 'separator';
|
||||||
li = document.createElement('li');
|
li = document.createElement('li');
|
||||||
li.appendChild(separator);
|
li.appendChild(separator);
|
||||||
list.appendChild(li);
|
list.appendChild(li);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var domItem = {};
|
||||||
|
|
||||||
|
// create a menu item
|
||||||
|
var li = document.createElement('li');
|
||||||
|
list.appendChild(li);
|
||||||
|
|
||||||
|
// create a button in the menu item
|
||||||
|
var button = document.createElement('button');
|
||||||
|
button.className = item.className;
|
||||||
|
domItem.button = button;
|
||||||
|
if (item.title) {
|
||||||
|
button.title = item.title;
|
||||||
}
|
}
|
||||||
else {
|
if (item.click) {
|
||||||
var domItem = {};
|
button.onclick = function () {
|
||||||
|
me.hide();
|
||||||
|
item.click();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
li.appendChild(button);
|
||||||
|
|
||||||
// create a menu item
|
// create the contents of the button
|
||||||
var li = document.createElement('li');
|
if (item.submenu) {
|
||||||
list.appendChild(li);
|
// add the icon to the button
|
||||||
|
var divIcon = document.createElement('div');
|
||||||
|
divIcon.className = 'icon';
|
||||||
|
button.appendChild(divIcon);
|
||||||
|
button.appendChild(document.createTextNode(item.text));
|
||||||
|
|
||||||
// create a button in the menu item
|
var buttonSubmenu;
|
||||||
var button = document.createElement('button');
|
|
||||||
button.className = item.className;
|
|
||||||
domItem.button = button;
|
|
||||||
if (item.title) {
|
|
||||||
button.title = item.title;
|
|
||||||
}
|
|
||||||
if (item.click) {
|
if (item.click) {
|
||||||
button.onclick = function () {
|
// submenu and a button with a click handler
|
||||||
me.hide();
|
button.className += ' default';
|
||||||
item.click();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
li.appendChild(button);
|
|
||||||
|
|
||||||
// create the contents of the button
|
var buttonExpand = document.createElement('button');
|
||||||
if (item.submenu) {
|
domItem.buttonExpand = buttonExpand;
|
||||||
// add the icon to the button
|
buttonExpand.className = 'expand';
|
||||||
var divIcon = document.createElement('div');
|
buttonExpand.innerHTML = '<div class="expand"></div>';
|
||||||
divIcon.className = 'icon';
|
li.appendChild(buttonExpand);
|
||||||
button.appendChild(divIcon);
|
if (item.submenuTitle) {
|
||||||
button.appendChild(document.createTextNode(item.text));
|
buttonExpand.title = item.submenuTitle;
|
||||||
|
|
||||||
var buttonSubmenu;
|
|
||||||
if (item.click) {
|
|
||||||
// submenu and a button with a click handler
|
|
||||||
button.className += ' default';
|
|
||||||
|
|
||||||
var buttonExpand = document.createElement('button');
|
|
||||||
domItem.buttonExpand = buttonExpand;
|
|
||||||
buttonExpand.className = 'expand';
|
|
||||||
buttonExpand.innerHTML = '<div class="expand"></div>';
|
|
||||||
li.appendChild(buttonExpand);
|
|
||||||
if (item.submenuTitle) {
|
|
||||||
buttonExpand.title = item.submenuTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonSubmenu = buttonExpand;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// submenu and a button without a click handler
|
|
||||||
var divExpand = document.createElement('div');
|
|
||||||
divExpand.className = 'expand';
|
|
||||||
button.appendChild(divExpand);
|
|
||||||
|
|
||||||
buttonSubmenu = button;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// attach a handler to expand/collapse the submenu
|
buttonSubmenu = buttonExpand;
|
||||||
buttonSubmenu.onclick = function () {
|
|
||||||
me._onExpandItem(domItem);
|
|
||||||
buttonSubmenu.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
// create the submenu
|
|
||||||
var domSubItems = [];
|
|
||||||
domItem.subItems = domSubItems;
|
|
||||||
var ul = document.createElement('ul');
|
|
||||||
domItem.ul = ul;
|
|
||||||
ul.className = 'menu';
|
|
||||||
ul.style.height = '0';
|
|
||||||
li.appendChild(ul);
|
|
||||||
createMenuItems(ul, domSubItems, item.submenu);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// no submenu, just a button with clickhandler
|
// submenu and a button without a click handler
|
||||||
button.innerHTML = '<div class="icon"></div>' + item.text;
|
var divExpand = document.createElement('div');
|
||||||
|
divExpand.className = 'expand';
|
||||||
|
button.appendChild(divExpand);
|
||||||
|
|
||||||
|
buttonSubmenu = button;
|
||||||
}
|
}
|
||||||
|
|
||||||
domItems.push(domItem);
|
// attach a handler to expand/collapse the submenu
|
||||||
|
buttonSubmenu.onclick = function () {
|
||||||
|
me._onExpandItem(domItem);
|
||||||
|
buttonSubmenu.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
// create the submenu
|
||||||
|
var domSubItems = [];
|
||||||
|
domItem.subItems = domSubItems;
|
||||||
|
var ul = document.createElement('ul');
|
||||||
|
domItem.ul = ul;
|
||||||
|
ul.className = 'menu';
|
||||||
|
ul.style.height = '0';
|
||||||
|
li.appendChild(ul);
|
||||||
|
createMenuItems(ul, domSubItems, item.submenu);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// no submenu, just a button with clickhandler
|
||||||
|
button.innerHTML = '<div class="icon"></div>' + item.text;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
createMenuItems(list, this.dom.items, items);
|
|
||||||
|
|
||||||
// TODO: when the editor is small, show the submenu on the right instead of inline?
|
domItems.push(domItem);
|
||||||
|
}
|
||||||
// calculate the max height of the menu with one submenu expanded
|
|
||||||
this.maxHeight = 0; // height in pixels
|
|
||||||
items.forEach(function (item) {
|
|
||||||
var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24;
|
|
||||||
me.maxHeight = Math.max(me.maxHeight, height);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
createMenuItems(list, this.dom.items, items);
|
||||||
|
|
||||||
/**
|
// TODO: when the editor is small, show the submenu on the right instead of inline?
|
||||||
* Get the currently visible buttons
|
|
||||||
* @return {Array.<HTMLElement>} buttons
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ContextMenu.prototype._getVisibleButtons = function () {
|
|
||||||
var buttons = [];
|
|
||||||
var me = this;
|
|
||||||
this.dom.items.forEach(function (item) {
|
|
||||||
buttons.push(item.button);
|
|
||||||
if (item.buttonExpand) {
|
|
||||||
buttons.push(item.buttonExpand);
|
|
||||||
}
|
|
||||||
if (item.subItems && item == me.expandedItem) {
|
|
||||||
item.subItems.forEach(function (subItem) {
|
|
||||||
buttons.push(subItem.button);
|
|
||||||
if (subItem.buttonExpand) {
|
|
||||||
buttons.push(subItem.buttonExpand);
|
|
||||||
}
|
|
||||||
// TODO: change to fully recursive method
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return buttons;
|
// calculate the max height of the menu with one submenu expanded
|
||||||
};
|
this.maxHeight = 0; // height in pixels
|
||||||
|
items.forEach(function (item) {
|
||||||
|
var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24;
|
||||||
|
me.maxHeight = Math.max(me.maxHeight, height);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently visible buttons
|
||||||
|
* @return {Array.<HTMLElement>} buttons
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ContextMenu.prototype._getVisibleButtons = function () {
|
||||||
|
var buttons = [];
|
||||||
|
var me = this;
|
||||||
|
this.dom.items.forEach(function (item) {
|
||||||
|
buttons.push(item.button);
|
||||||
|
if (item.buttonExpand) {
|
||||||
|
buttons.push(item.buttonExpand);
|
||||||
|
}
|
||||||
|
if (item.subItems && item == me.expandedItem) {
|
||||||
|
item.subItems.forEach(function (subItem) {
|
||||||
|
buttons.push(subItem.button);
|
||||||
|
if (subItem.buttonExpand) {
|
||||||
|
buttons.push(subItem.buttonExpand);
|
||||||
|
}
|
||||||
|
// TODO: change to fully recursive method
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return buttons;
|
||||||
|
};
|
||||||
|
|
||||||
// currently displayed context menu, a singleton. We may only have one visible context menu
|
// currently displayed context menu, a singleton. We may only have one visible context menu
|
||||||
ContextMenu.visibleMenu = undefined;
|
ContextMenu.visibleMenu = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach the menu to an anchor
|
* Attach the menu to an anchor
|
||||||
* @param {HTMLElement} anchor
|
* @param {HTMLElement} anchor
|
||||||
*/
|
*/
|
||||||
ContextMenu.prototype.show = function (anchor) {
|
ContextMenu.prototype.show = function (anchor) {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
||||||
// calculate whether the menu fits below the anchor
|
// calculate whether the menu fits below the anchor
|
||||||
var windowHeight = window.innerHeight,
|
var windowHeight = window.innerHeight,
|
||||||
windowScroll = (window.pageYOffset || document.scrollTop || 0),
|
windowScroll = (window.pageYOffset || document.scrollTop || 0),
|
||||||
windowBottom = windowHeight + windowScroll,
|
windowBottom = windowHeight + windowScroll,
|
||||||
anchorHeight = anchor.offsetHeight,
|
anchorHeight = anchor.offsetHeight,
|
||||||
menuHeight = this.maxHeight;
|
menuHeight = this.maxHeight;
|
||||||
|
|
||||||
// position the menu
|
// position the menu
|
||||||
var left = util.getAbsoluteLeft(anchor);
|
var left = util.getAbsoluteLeft(anchor);
|
||||||
var top = util.getAbsoluteTop(anchor);
|
var top = util.getAbsoluteTop(anchor);
|
||||||
if (top + anchorHeight + menuHeight < windowBottom) {
|
if (top + anchorHeight + menuHeight < windowBottom) {
|
||||||
// display the menu below the anchor
|
// display the menu below the anchor
|
||||||
this.dom.menu.style.left = left + 'px';
|
this.dom.menu.style.left = left + 'px';
|
||||||
this.dom.menu.style.top = (top + anchorHeight) + 'px';
|
this.dom.menu.style.top = (top + 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 = left + 'px';
|
this.dom.menu.style.left = left + 'px';
|
||||||
this.dom.menu.style.top = '';
|
this.dom.menu.style.top = '';
|
||||||
this.dom.menu.style.bottom = (windowHeight - top) + 'px';
|
this.dom.menu.style.bottom = (windowHeight - top) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
// attach the menu to the document
|
// attach the menu to the document
|
||||||
document.body.appendChild(this.dom.menu);
|
document.body.appendChild(this.dom.menu);
|
||||||
|
|
||||||
// create and attach event listeners
|
// create and attach event listeners
|
||||||
var me = this;
|
var me = this;
|
||||||
var list = this.dom.list;
|
var list = this.dom.list;
|
||||||
this.eventListeners.mousedown = util.addEventListener(
|
this.eventListeners.mousedown = util.addEventListener(
|
||||||
document, 'mousedown', function (event) {
|
document, 'mousedown', function (event) {
|
||||||
// hide menu on click outside of the menu
|
// hide menu on click outside of the menu
|
||||||
var target = event.target;
|
var target = event.target;
|
||||||
if ((target != list) && !me._isChildOf(target, list)) {
|
if ((target != list) && !me._isChildOf(target, list)) {
|
||||||
me.hide();
|
me.hide();
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.eventListeners.mousewheel = util.addEventListener(
|
|
||||||
document, 'mousewheel', function (event) {
|
|
||||||
// block scrolling when context menu is visible
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
}
|
||||||
this.eventListeners.keydown = util.addEventListener(
|
});
|
||||||
document, 'keydown', function (event) {
|
this.eventListeners.mousewheel = util.addEventListener(
|
||||||
me._onKeyDown(event);
|
document, 'mousewheel', function (event) {
|
||||||
});
|
// block scrolling when context menu is visible
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
this.eventListeners.keydown = util.addEventListener(
|
||||||
|
document, 'keydown', function (event) {
|
||||||
|
me._onKeyDown(event);
|
||||||
|
});
|
||||||
|
|
||||||
// move focus to the first button in the context menu
|
// move focus to the first button in the context menu
|
||||||
this.selection = util.getSelection();
|
this.selection = util.getSelection();
|
||||||
this.anchor = anchor;
|
this.anchor = anchor;
|
||||||
|
setTimeout(function () {
|
||||||
|
me.dom.focusButton.focus();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (ContextMenu.visibleMenu) {
|
||||||
|
ContextMenu.visibleMenu.hide();
|
||||||
|
}
|
||||||
|
ContextMenu.visibleMenu = this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the context menu if visible
|
||||||
|
*/
|
||||||
|
ContextMenu.prototype.hide = function () {
|
||||||
|
// remove the menu from the DOM
|
||||||
|
if (this.dom.menu.parentNode) {
|
||||||
|
this.dom.menu.parentNode.removeChild(this.dom.menu);
|
||||||
|
if (this.onClose) {
|
||||||
|
this.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all event listeners
|
||||||
|
// all event listeners are supposed to be attached to document.
|
||||||
|
for (var name in this.eventListeners) {
|
||||||
|
if (this.eventListeners.hasOwnProperty(name)) {
|
||||||
|
var fn = this.eventListeners[name];
|
||||||
|
if (fn) {
|
||||||
|
util.removeEventListener(document, name, fn);
|
||||||
|
}
|
||||||
|
delete this.eventListeners[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContextMenu.visibleMenu == this) {
|
||||||
|
ContextMenu.visibleMenu = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand a submenu
|
||||||
|
* Any currently expanded submenu will be hided.
|
||||||
|
* @param {Object} domItem
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ContextMenu.prototype._onExpandItem = function (domItem) {
|
||||||
|
var me = this;
|
||||||
|
var alreadyVisible = (domItem == this.expandedItem);
|
||||||
|
|
||||||
|
// hide the currently visible submenu
|
||||||
|
var expandedItem = this.expandedItem;
|
||||||
|
if (expandedItem) {
|
||||||
|
//var ul = expandedItem.ul;
|
||||||
|
expandedItem.ul.style.height = '0';
|
||||||
|
expandedItem.ul.style.padding = '';
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
me.dom.focusButton.focus();
|
if (me.expandedItem != expandedItem) {
|
||||||
|
expandedItem.ul.style.display = '';
|
||||||
|
util.removeClassName(expandedItem.ul.parentNode, 'selected');
|
||||||
|
}
|
||||||
|
}, 300); // timeout duration must match the css transition duration
|
||||||
|
this.expandedItem = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alreadyVisible) {
|
||||||
|
var ul = domItem.ul;
|
||||||
|
ul.style.display = 'block';
|
||||||
|
var height = ul.clientHeight; // force a reflow in Firefox
|
||||||
|
setTimeout(function () {
|
||||||
|
if (me.expandedItem == domItem) {
|
||||||
|
ul.style.height = (ul.childNodes.length * 24) + 'px';
|
||||||
|
ul.style.padding = '5px 10px';
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
util.addClassName(ul.parentNode, 'selected');
|
||||||
|
this.expandedItem = domItem;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (ContextMenu.visibleMenu) {
|
/**
|
||||||
ContextMenu.visibleMenu.hide();
|
* Handle onkeydown event
|
||||||
|
* @param {Event} event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ContextMenu.prototype._onKeyDown = function (event) {
|
||||||
|
var target = event.target;
|
||||||
|
var keynum = event.which;
|
||||||
|
var handled = false;
|
||||||
|
var buttons, targetIndex, prevButton, nextButton;
|
||||||
|
|
||||||
|
if (keynum == 27) { // ESC
|
||||||
|
// hide the menu on ESC key
|
||||||
|
|
||||||
|
// restore previous selection and focus
|
||||||
|
if (this.selection) {
|
||||||
|
util.setSelection(this.selection);
|
||||||
|
}
|
||||||
|
if (this.anchor) {
|
||||||
|
this.anchor.focus();
|
||||||
}
|
}
|
||||||
ContextMenu.visibleMenu = this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
this.hide();
|
||||||
* Hide the context menu if visible
|
|
||||||
*/
|
handled = true;
|
||||||
ContextMenu.prototype.hide = function () {
|
}
|
||||||
// remove the menu from the DOM
|
else if (keynum == 9) { // Tab
|
||||||
if (this.dom.menu.parentNode) {
|
if (!event.shiftKey) { // Tab
|
||||||
this.dom.menu.parentNode.removeChild(this.dom.menu);
|
buttons = this._getVisibleButtons();
|
||||||
if (this.onClose) {
|
targetIndex = buttons.indexOf(target);
|
||||||
this.onClose();
|
if (targetIndex == buttons.length - 1) {
|
||||||
|
// move to first button
|
||||||
|
buttons[0].focus();
|
||||||
|
handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else { // Shift+Tab
|
||||||
// remove all event listeners
|
buttons = this._getVisibleButtons();
|
||||||
// all event listeners are supposed to be attached to document.
|
targetIndex = buttons.indexOf(target);
|
||||||
for (var name in this.eventListeners) {
|
if (targetIndex == 0) {
|
||||||
if (this.eventListeners.hasOwnProperty(name)) {
|
// move to last button
|
||||||
var fn = this.eventListeners[name];
|
buttons[buttons.length - 1].focus();
|
||||||
if (fn) {
|
handled = true;
|
||||||
util.removeEventListener(document, name, fn);
|
|
||||||
}
|
|
||||||
delete this.eventListeners[name];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (ContextMenu.visibleMenu == this) {
|
else if (keynum == 37) { // Arrow Left
|
||||||
ContextMenu.visibleMenu = undefined;
|
if (target.className == 'expand') {
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expand a submenu
|
|
||||||
* Any currently expanded submenu will be hided.
|
|
||||||
* @param {Object} domItem
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ContextMenu.prototype._onExpandItem = function (domItem) {
|
|
||||||
var me = this;
|
|
||||||
var alreadyVisible = (domItem == this.expandedItem);
|
|
||||||
|
|
||||||
// hide the currently visible submenu
|
|
||||||
var expandedItem = this.expandedItem;
|
|
||||||
if (expandedItem) {
|
|
||||||
//var ul = expandedItem.ul;
|
|
||||||
expandedItem.ul.style.height = '0';
|
|
||||||
expandedItem.ul.style.padding = '';
|
|
||||||
setTimeout(function () {
|
|
||||||
if (me.expandedItem != expandedItem) {
|
|
||||||
expandedItem.ul.style.display = '';
|
|
||||||
util.removeClassName(expandedItem.ul.parentNode, 'selected');
|
|
||||||
}
|
|
||||||
}, 300); // timeout duration must match the css transition duration
|
|
||||||
this.expandedItem = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!alreadyVisible) {
|
|
||||||
var ul = domItem.ul;
|
|
||||||
ul.style.display = 'block';
|
|
||||||
var height = ul.clientHeight; // force a reflow in Firefox
|
|
||||||
setTimeout(function () {
|
|
||||||
if (me.expandedItem == domItem) {
|
|
||||||
ul.style.height = (ul.childNodes.length * 24) + 'px';
|
|
||||||
ul.style.padding = '5px 10px';
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
util.addClassName(ul.parentNode, 'selected');
|
|
||||||
this.expandedItem = domItem;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle onkeydown event
|
|
||||||
* @param {Event} event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ContextMenu.prototype._onKeyDown = function (event) {
|
|
||||||
var target = event.target;
|
|
||||||
var keynum = event.which;
|
|
||||||
var handled = false;
|
|
||||||
var buttons, targetIndex, prevButton, nextButton;
|
|
||||||
|
|
||||||
if (keynum == 27) { // ESC
|
|
||||||
// hide the menu on ESC key
|
|
||||||
|
|
||||||
// restore previous selection and focus
|
|
||||||
if (this.selection) {
|
|
||||||
util.setSelection(this.selection);
|
|
||||||
}
|
|
||||||
if (this.anchor) {
|
|
||||||
this.anchor.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hide();
|
|
||||||
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
else if (keynum == 9) { // Tab
|
|
||||||
if (!event.shiftKey) { // Tab
|
|
||||||
buttons = this._getVisibleButtons();
|
|
||||||
targetIndex = buttons.indexOf(target);
|
|
||||||
if (targetIndex == buttons.length - 1) {
|
|
||||||
// move to first button
|
|
||||||
buttons[0].focus();
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else { // Shift+Tab
|
|
||||||
buttons = this._getVisibleButtons();
|
|
||||||
targetIndex = buttons.indexOf(target);
|
|
||||||
if (targetIndex == 0) {
|
|
||||||
// move to last button
|
|
||||||
buttons[buttons.length - 1].focus();
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (keynum == 37) { // Arrow Left
|
|
||||||
if (target.className == 'expand') {
|
|
||||||
buttons = this._getVisibleButtons();
|
|
||||||
targetIndex = buttons.indexOf(target);
|
|
||||||
prevButton = buttons[targetIndex - 1];
|
|
||||||
if (prevButton) {
|
|
||||||
prevButton.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
else if (keynum == 38) { // Arrow Up
|
|
||||||
buttons = this._getVisibleButtons();
|
buttons = this._getVisibleButtons();
|
||||||
targetIndex = buttons.indexOf(target);
|
targetIndex = buttons.indexOf(target);
|
||||||
prevButton = buttons[targetIndex - 1];
|
prevButton = buttons[targetIndex - 1];
|
||||||
if (prevButton && prevButton.className == 'expand') {
|
|
||||||
// skip expand button
|
|
||||||
prevButton = buttons[targetIndex - 2];
|
|
||||||
}
|
|
||||||
if (!prevButton) {
|
|
||||||
// move to last button
|
|
||||||
prevButton = buttons[buttons.length - 1];
|
|
||||||
}
|
|
||||||
if (prevButton) {
|
if (prevButton) {
|
||||||
prevButton.focus();
|
prevButton.focus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
else if (keynum == 38) { // Arrow Up
|
||||||
|
buttons = this._getVisibleButtons();
|
||||||
|
targetIndex = buttons.indexOf(target);
|
||||||
|
prevButton = buttons[targetIndex - 1];
|
||||||
|
if (prevButton && prevButton.className == 'expand') {
|
||||||
|
// skip expand button
|
||||||
|
prevButton = buttons[targetIndex - 2];
|
||||||
|
}
|
||||||
|
if (!prevButton) {
|
||||||
|
// move to last button
|
||||||
|
prevButton = buttons[buttons.length - 1];
|
||||||
|
}
|
||||||
|
if (prevButton) {
|
||||||
|
prevButton.focus();
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
else if (keynum == 39) { // Arrow Right
|
||||||
|
buttons = this._getVisibleButtons();
|
||||||
|
targetIndex = buttons.indexOf(target);
|
||||||
|
nextButton = buttons[targetIndex + 1];
|
||||||
|
if (nextButton && nextButton.className == 'expand') {
|
||||||
|
nextButton.focus();
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
else if (keynum == 40) { // Arrow Down
|
||||||
|
buttons = this._getVisibleButtons();
|
||||||
|
targetIndex = buttons.indexOf(target);
|
||||||
|
nextButton = buttons[targetIndex + 1];
|
||||||
|
if (nextButton && nextButton.className == 'expand') {
|
||||||
|
// skip expand button
|
||||||
|
nextButton = buttons[targetIndex + 2];
|
||||||
|
}
|
||||||
|
if (!nextButton) {
|
||||||
|
// move to first button
|
||||||
|
nextButton = buttons[0];
|
||||||
|
}
|
||||||
|
if (nextButton) {
|
||||||
|
nextButton.focus();
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
else if (keynum == 39) { // Arrow Right
|
handled = true;
|
||||||
buttons = this._getVisibleButtons();
|
}
|
||||||
targetIndex = buttons.indexOf(target);
|
// TODO: arrow left and right
|
||||||
nextButton = buttons[targetIndex + 1];
|
|
||||||
if (nextButton && nextButton.className == 'expand') {
|
|
||||||
nextButton.focus();
|
|
||||||
}
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
else if (keynum == 40) { // Arrow Down
|
|
||||||
buttons = this._getVisibleButtons();
|
|
||||||
targetIndex = buttons.indexOf(target);
|
|
||||||
nextButton = buttons[targetIndex + 1];
|
|
||||||
if (nextButton && nextButton.className == 'expand') {
|
|
||||||
// skip expand button
|
|
||||||
nextButton = buttons[targetIndex + 2];
|
|
||||||
}
|
|
||||||
if (!nextButton) {
|
|
||||||
// move to first button
|
|
||||||
nextButton = buttons[0];
|
|
||||||
}
|
|
||||||
if (nextButton) {
|
|
||||||
nextButton.focus();
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
// TODO: arrow left and right
|
|
||||||
|
|
||||||
if (handled) {
|
if (handled) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if an element is a child of a parent element.
|
||||||
|
* @param {Element} child
|
||||||
|
* @param {Element} parent
|
||||||
|
* @return {boolean} isChild
|
||||||
|
*/
|
||||||
|
ContextMenu.prototype._isChildOf = function (child, parent) {
|
||||||
|
var e = child.parentNode;
|
||||||
|
while (e) {
|
||||||
|
if (e == parent) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
e = e.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return false;
|
||||||
* Test if an element is a child of a parent element.
|
};
|
||||||
* @param {Element} child
|
|
||||||
* @param {Element} parent
|
|
||||||
* @return {boolean} isChild
|
|
||||||
*/
|
|
||||||
ContextMenu.prototype._isChildOf = function (child, parent) {
|
|
||||||
var e = child.parentNode;
|
|
||||||
while (e) {
|
|
||||||
if (e == parent) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
e = e.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
module.exports = ContextMenu;
|
||||||
};
|
|
||||||
|
|
||||||
return ContextMenu;
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,87 +1,84 @@
|
||||||
define(function () {
|
/**
|
||||||
|
* The highlighter can highlight/unhighlight a node, and
|
||||||
|
* animate the visibility of a context menu.
|
||||||
|
* @constructor Highlighter
|
||||||
|
*/
|
||||||
|
function Highlighter () {
|
||||||
|
this.locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The highlighter can highlight/unhighlight a node, and
|
* Hightlight given node and its childs
|
||||||
* animate the visibility of a context menu.
|
* @param {Node} node
|
||||||
* @constructor Highlighter
|
*/
|
||||||
*/
|
Highlighter.prototype.highlight = function (node) {
|
||||||
function Highlighter () {
|
if (this.locked) {
|
||||||
this.locked = false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (this.node != node) {
|
||||||
* Hightlight given node and its childs
|
// unhighlight current node
|
||||||
* @param {Node} node
|
|
||||||
*/
|
|
||||||
Highlighter.prototype.highlight = function (node) {
|
|
||||||
if (this.locked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.node != node) {
|
|
||||||
// unhighlight current node
|
|
||||||
if (this.node) {
|
|
||||||
this.node.setHighlight(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// highlight new node
|
|
||||||
this.node = node;
|
|
||||||
this.node.setHighlight(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel any current timeout
|
|
||||||
this._cancelUnhighlight();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unhighlight currently highlighted node.
|
|
||||||
* Will be done after a delay
|
|
||||||
*/
|
|
||||||
Highlighter.prototype.unhighlight = function () {
|
|
||||||
if (this.locked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var me = this;
|
|
||||||
if (this.node) {
|
if (this.node) {
|
||||||
this._cancelUnhighlight();
|
this.node.setHighlight(false);
|
||||||
|
|
||||||
// do the unhighlighting after a small delay, to prevent re-highlighting
|
|
||||||
// the same node when moving from the drag-icon to the contextmenu-icon
|
|
||||||
// or vice versa.
|
|
||||||
this.unhighlightTimer = setTimeout(function () {
|
|
||||||
me.node.setHighlight(false);
|
|
||||||
me.node = undefined;
|
|
||||||
me.unhighlightTimer = undefined;
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
// highlight new node
|
||||||
* Cancel an unhighlight action (if before the timeout of the unhighlight action)
|
this.node = node;
|
||||||
* @private
|
this.node.setHighlight(true);
|
||||||
*/
|
}
|
||||||
Highlighter.prototype._cancelUnhighlight = function () {
|
|
||||||
if (this.unhighlightTimer) {
|
|
||||||
clearTimeout(this.unhighlightTimer);
|
|
||||||
this.unhighlightTimer = undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
// cancel any current timeout
|
||||||
* Lock highlighting or unhighlighting nodes.
|
this._cancelUnhighlight();
|
||||||
* methods highlight and unhighlight do not work while locked.
|
};
|
||||||
*/
|
|
||||||
Highlighter.prototype.lock = function () {
|
|
||||||
this.locked = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock highlighting or unhighlighting nodes
|
* Unhighlight currently highlighted node.
|
||||||
*/
|
* Will be done after a delay
|
||||||
Highlighter.prototype.unlock = function () {
|
*/
|
||||||
this.locked = false;
|
Highlighter.prototype.unhighlight = function () {
|
||||||
};
|
if (this.locked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return Highlighter;
|
var me = this;
|
||||||
});
|
if (this.node) {
|
||||||
|
this._cancelUnhighlight();
|
||||||
|
|
||||||
|
// do the unhighlighting after a small delay, to prevent re-highlighting
|
||||||
|
// the same node when moving from the drag-icon to the contextmenu-icon
|
||||||
|
// or vice versa.
|
||||||
|
this.unhighlightTimer = setTimeout(function () {
|
||||||
|
me.node.setHighlight(false);
|
||||||
|
me.node = undefined;
|
||||||
|
me.unhighlightTimer = undefined;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel an unhighlight action (if before the timeout of the unhighlight action)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Highlighter.prototype._cancelUnhighlight = function () {
|
||||||
|
if (this.unhighlightTimer) {
|
||||||
|
clearTimeout(this.unhighlightTimer);
|
||||||
|
this.unhighlightTimer = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock highlighting or unhighlighting nodes.
|
||||||
|
* methods highlight and unhighlight do not work while locked.
|
||||||
|
*/
|
||||||
|
Highlighter.prototype.lock = function () {
|
||||||
|
this.locked = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlock highlighting or unhighlighting nodes
|
||||||
|
*/
|
||||||
|
Highlighter.prototype.unlock = function () {
|
||||||
|
this.locked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Highlighter;
|
||||||
|
|
|
@ -1,223 +1,222 @@
|
||||||
define(['./util'], function (util) {
|
var util = require('./util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor History
|
* @constructor History
|
||||||
* Store action history, enables undo and redo
|
* Store action history, enables undo and redo
|
||||||
* @param {JSONEditor} editor
|
* @param {JSONEditor} editor
|
||||||
*/
|
*/
|
||||||
function History (editor) {
|
function History (editor) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
this.clear();
|
this.clear();
|
||||||
|
|
||||||
// map with all supported actions
|
// map with all supported actions
|
||||||
this.actions = {
|
this.actions = {
|
||||||
'editField': {
|
'editField': {
|
||||||
'undo': function (params) {
|
'undo': function (params) {
|
||||||
params.node.updateField(params.oldValue);
|
params.node.updateField(params.oldValue);
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.node.updateField(params.newValue);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'editValue': {
|
'redo': function (params) {
|
||||||
'undo': function (params) {
|
params.node.updateField(params.newValue);
|
||||||
params.node.updateValue(params.oldValue);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.node.updateValue(params.newValue);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'appendNode': {
|
|
||||||
'undo': function (params) {
|
|
||||||
params.parent.removeChild(params.node);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.parent.appendChild(params.node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'insertBeforeNode': {
|
|
||||||
'undo': function (params) {
|
|
||||||
params.parent.removeChild(params.node);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.parent.insertBefore(params.node, params.beforeNode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'insertAfterNode': {
|
|
||||||
'undo': function (params) {
|
|
||||||
params.parent.removeChild(params.node);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.parent.insertAfter(params.node, params.afterNode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'removeNode': {
|
|
||||||
'undo': function (params) {
|
|
||||||
var parent = params.parent;
|
|
||||||
var beforeNode = parent.childs[params.index] || parent.append;
|
|
||||||
parent.insertBefore(params.node, beforeNode);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.parent.removeChild(params.node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'duplicateNode': {
|
|
||||||
'undo': function (params) {
|
|
||||||
params.parent.removeChild(params.clone);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.parent.insertAfter(params.clone, params.node);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'changeType': {
|
|
||||||
'undo': function (params) {
|
|
||||||
params.node.changeType(params.oldType);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.node.changeType(params.newType);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'moveNode': {
|
|
||||||
'undo': function (params) {
|
|
||||||
params.startParent.moveTo(params.node, params.startIndex);
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
params.endParent.moveTo(params.node, params.endIndex);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'sort': {
|
|
||||||
'undo': function (params) {
|
|
||||||
var node = params.node;
|
|
||||||
node.hideChilds();
|
|
||||||
node.sort = params.oldSort;
|
|
||||||
node.childs = params.oldChilds;
|
|
||||||
node.showChilds();
|
|
||||||
},
|
|
||||||
'redo': function (params) {
|
|
||||||
var node = params.node;
|
|
||||||
node.hideChilds();
|
|
||||||
node.sort = params.newSort;
|
|
||||||
node.childs = params.newChilds;
|
|
||||||
node.showChilds();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'editValue': {
|
||||||
|
'undo': function (params) {
|
||||||
|
params.node.updateValue(params.oldValue);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.node.updateValue(params.newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'appendNode': {
|
||||||
|
'undo': function (params) {
|
||||||
|
params.parent.removeChild(params.node);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.parent.appendChild(params.node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'insertBeforeNode': {
|
||||||
|
'undo': function (params) {
|
||||||
|
params.parent.removeChild(params.node);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.parent.insertBefore(params.node, params.beforeNode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'insertAfterNode': {
|
||||||
|
'undo': function (params) {
|
||||||
|
params.parent.removeChild(params.node);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.parent.insertAfter(params.node, params.afterNode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'removeNode': {
|
||||||
|
'undo': function (params) {
|
||||||
|
var parent = params.parent;
|
||||||
|
var beforeNode = parent.childs[params.index] || parent.append;
|
||||||
|
parent.insertBefore(params.node, beforeNode);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.parent.removeChild(params.node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'duplicateNode': {
|
||||||
|
'undo': function (params) {
|
||||||
|
params.parent.removeChild(params.clone);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.parent.insertAfter(params.clone, params.node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'changeType': {
|
||||||
|
'undo': function (params) {
|
||||||
|
params.node.changeType(params.oldType);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.node.changeType(params.newType);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'moveNode': {
|
||||||
|
'undo': function (params) {
|
||||||
|
params.startParent.moveTo(params.node, params.startIndex);
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
params.endParent.moveTo(params.node, params.endIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'sort': {
|
||||||
|
'undo': function (params) {
|
||||||
|
var node = params.node;
|
||||||
|
node.hideChilds();
|
||||||
|
node.sort = params.oldSort;
|
||||||
|
node.childs = params.oldChilds;
|
||||||
|
node.showChilds();
|
||||||
|
},
|
||||||
|
'redo': function (params) {
|
||||||
|
var node = params.node;
|
||||||
|
node.hideChilds();
|
||||||
|
node.sort = params.newSort;
|
||||||
|
node.childs = params.newChilds;
|
||||||
|
node.showChilds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: restore the original caret position and selection with each undo
|
// TODO: restore the original caret position and selection with each undo
|
||||||
// TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
|
// TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method onChange is executed when the History is changed, and can
|
||||||
|
* be overloaded.
|
||||||
|
*/
|
||||||
|
History.prototype.onChange = function () {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new action to the history
|
||||||
|
* @param {String} action The executed action. Available actions: "editField",
|
||||||
|
* "editValue", "changeType", "appendNode",
|
||||||
|
* "removeNode", "duplicateNode", "moveNode"
|
||||||
|
* @param {Object} params Object containing parameters describing the change.
|
||||||
|
* The parameters in params depend on the action (for
|
||||||
|
* example for "editValue" the Node, old value, and new
|
||||||
|
* value are provided). params contains all information
|
||||||
|
* needed to undo or redo the action.
|
||||||
|
*/
|
||||||
|
History.prototype.add = function (action, params) {
|
||||||
|
this.index++;
|
||||||
|
this.history[this.index] = {
|
||||||
|
'action': action,
|
||||||
|
'params': params,
|
||||||
|
'timestamp': new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
// remove redo actions which are invalid now
|
||||||
|
if (this.index < this.history.length - 1) {
|
||||||
|
this.history.splice(this.index + 1, this.history.length - this.index - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// fire onchange event
|
||||||
* The method onChange is executed when the History is changed, and can
|
this.onChange();
|
||||||
* be overloaded.
|
};
|
||||||
*/
|
|
||||||
History.prototype.onChange = function () {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new action to the history
|
* Clear history
|
||||||
* @param {String} action The executed action. Available actions: "editField",
|
*/
|
||||||
* "editValue", "changeType", "appendNode",
|
History.prototype.clear = function () {
|
||||||
* "removeNode", "duplicateNode", "moveNode"
|
this.history = [];
|
||||||
* @param {Object} params Object containing parameters describing the change.
|
this.index = -1;
|
||||||
* The parameters in params depend on the action (for
|
|
||||||
* example for "editValue" the Node, old value, and new
|
// fire onchange event
|
||||||
* value are provided). params contains all information
|
this.onChange();
|
||||||
* needed to undo or redo the action.
|
};
|
||||||
*/
|
|
||||||
History.prototype.add = function (action, params) {
|
/**
|
||||||
|
* Check if there is an action available for undo
|
||||||
|
* @return {Boolean} canUndo
|
||||||
|
*/
|
||||||
|
History.prototype.canUndo = function () {
|
||||||
|
return (this.index >= 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is an action available for redo
|
||||||
|
* @return {Boolean} canRedo
|
||||||
|
*/
|
||||||
|
History.prototype.canRedo = function () {
|
||||||
|
return (this.index < this.history.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo the last action
|
||||||
|
*/
|
||||||
|
History.prototype.undo = function () {
|
||||||
|
if (this.canUndo()) {
|
||||||
|
var obj = this.history[this.index];
|
||||||
|
if (obj) {
|
||||||
|
var action = this.actions[obj.action];
|
||||||
|
if (action && action.undo) {
|
||||||
|
action.undo(obj.params);
|
||||||
|
if (obj.params.oldSelection) {
|
||||||
|
this.editor.setSelection(obj.params.oldSelection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
util.log('Error: unknown action "' + obj.action + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.index--;
|
||||||
|
|
||||||
|
// fire onchange event
|
||||||
|
this.onChange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redo the last action
|
||||||
|
*/
|
||||||
|
History.prototype.redo = function () {
|
||||||
|
if (this.canRedo()) {
|
||||||
this.index++;
|
this.index++;
|
||||||
this.history[this.index] = {
|
|
||||||
'action': action,
|
|
||||||
'params': params,
|
|
||||||
'timestamp': new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
// remove redo actions which are invalid now
|
var obj = this.history[this.index];
|
||||||
if (this.index < this.history.length - 1) {
|
if (obj) {
|
||||||
this.history.splice(this.index + 1, this.history.length - this.index - 1);
|
var action = this.actions[obj.action];
|
||||||
|
if (action && action.redo) {
|
||||||
|
action.redo(obj.params);
|
||||||
|
if (obj.params.newSelection) {
|
||||||
|
this.editor.setSelection(obj.params.newSelection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
util.log('Error: unknown action "' + obj.action + '"');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fire onchange event
|
// fire onchange event
|
||||||
this.onChange();
|
this.onChange();
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
module.exports = History;
|
||||||
* Clear history
|
|
||||||
*/
|
|
||||||
History.prototype.clear = function () {
|
|
||||||
this.history = [];
|
|
||||||
this.index = -1;
|
|
||||||
|
|
||||||
// fire onchange event
|
|
||||||
this.onChange();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there is an action available for undo
|
|
||||||
* @return {Boolean} canUndo
|
|
||||||
*/
|
|
||||||
History.prototype.canUndo = function () {
|
|
||||||
return (this.index >= 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if there is an action available for redo
|
|
||||||
* @return {Boolean} canRedo
|
|
||||||
*/
|
|
||||||
History.prototype.canRedo = function () {
|
|
||||||
return (this.index < this.history.length - 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undo the last action
|
|
||||||
*/
|
|
||||||
History.prototype.undo = function () {
|
|
||||||
if (this.canUndo()) {
|
|
||||||
var obj = this.history[this.index];
|
|
||||||
if (obj) {
|
|
||||||
var action = this.actions[obj.action];
|
|
||||||
if (action && action.undo) {
|
|
||||||
action.undo(obj.params);
|
|
||||||
if (obj.params.oldSelection) {
|
|
||||||
this.editor.setSelection(obj.params.oldSelection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
util.log('Error: unknown action "' + obj.action + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.index--;
|
|
||||||
|
|
||||||
// fire onchange event
|
|
||||||
this.onChange();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redo the last action
|
|
||||||
*/
|
|
||||||
History.prototype.redo = function () {
|
|
||||||
if (this.canRedo()) {
|
|
||||||
this.index++;
|
|
||||||
|
|
||||||
var obj = this.history[this.index];
|
|
||||||
if (obj) {
|
|
||||||
var action = this.actions[obj.action];
|
|
||||||
if (action && action.redo) {
|
|
||||||
action.redo(obj.params);
|
|
||||||
if (obj.params.newSelection) {
|
|
||||||
this.editor.setSelection(obj.params.newSelection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
util.log('Error: unknown action "' + obj.action + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fire onchange event
|
|
||||||
this.onChange();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return History;
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,261 +1,262 @@
|
||||||
define(['./treemode', './textmode', './util'], function (treemode, textmode, util) {
|
var treemode = require('./treemode');
|
||||||
|
var textmode = require('./textmode');
|
||||||
|
var util = require('./util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor JSONEditor
|
* @constructor JSONEditor
|
||||||
* @param {Element} container Container element
|
* @param {Element} container Container element
|
||||||
* @param {Object} [options] Object with options. available options:
|
* @param {Object} [options] Object with options. available options:
|
||||||
* {String} mode Editor mode. Available values:
|
* {String} mode Editor mode. Available values:
|
||||||
* 'tree' (default), 'view',
|
* 'tree' (default), 'view',
|
||||||
* 'form', 'text', and 'code'.
|
* 'form', 'text', and 'code'.
|
||||||
* {function} change Callback method, triggered
|
* {function} change Callback method, triggered
|
||||||
* on change of contents
|
* on change of contents
|
||||||
* {Boolean} search Enable search box.
|
* {Boolean} search Enable search box.
|
||||||
* True by default
|
* True by default
|
||||||
* Only applicable for modes
|
* Only applicable for modes
|
||||||
* 'tree', 'view', and 'form'
|
* 'tree', 'view', and 'form'
|
||||||
* {Boolean} history Enable history (undo/redo).
|
* {Boolean} history Enable history (undo/redo).
|
||||||
* True by default
|
* True by default
|
||||||
* Only applicable for modes
|
* Only applicable for modes
|
||||||
* 'tree', 'view', and 'form'
|
* 'tree', 'view', and 'form'
|
||||||
* {String} name Field name for the root node.
|
* {String} name Field name for the root node.
|
||||||
* Only applicable for modes
|
* Only applicable for modes
|
||||||
* 'tree', 'view', and 'form'
|
* 'tree', 'view', and 'form'
|
||||||
* {Number} indentation Number of indentation
|
* {Number} indentation Number of indentation
|
||||||
* spaces. 4 by default.
|
* spaces. 4 by default.
|
||||||
* Only applicable for
|
* Only applicable for
|
||||||
* modes 'text' and 'code'
|
* modes 'text' and 'code'
|
||||||
* @param {Object | undefined} json JSON object
|
* @param {Object | undefined} json JSON object
|
||||||
*/
|
*/
|
||||||
function JSONEditor (container, options, json) {
|
function JSONEditor (container, options, json) {
|
||||||
if (!(this instanceof JSONEditor)) {
|
if (!(this instanceof JSONEditor)) {
|
||||||
throw new Error('JSONEditor constructor called without "new".');
|
throw new Error('JSONEditor constructor called without "new".');
|
||||||
}
|
|
||||||
|
|
||||||
// check for unsupported browser (IE8 and older)
|
|
||||||
var ieVersion = util.getInternetExplorerVersion();
|
|
||||||
if (ieVersion != -1 && ieVersion < 9) {
|
|
||||||
throw new Error('Unsupported browser, IE9 or newer required. ' +
|
|
||||||
'Please install the newest version of your browser.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.length) {
|
|
||||||
this._create(container, options, json);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// check for unsupported browser (IE8 and older)
|
||||||
* Configuration for all registered modes. Example:
|
var ieVersion = util.getInternetExplorerVersion();
|
||||||
* {
|
if (ieVersion != -1 && ieVersion < 9) {
|
||||||
* tree: {
|
throw new Error('Unsupported browser, IE9 or newer required. ' +
|
||||||
* mixin: TreeEditor,
|
'Please install the newest version of your browser.');
|
||||||
* data: 'json'
|
}
|
||||||
* },
|
|
||||||
* text: {
|
|
||||||
* mixin: TextEditor,
|
|
||||||
* data: 'text'
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @type { Object.<String, {mixin: Object, data: String} > }
|
|
||||||
*/
|
|
||||||
JSONEditor.modes = {};
|
|
||||||
|
|
||||||
/**
|
if (arguments.length) {
|
||||||
* Create the JSONEditor
|
this._create(container, options, json);
|
||||||
* @param {Element} container Container element
|
}
|
||||||
* @param {Object} [options] See description in constructor
|
}
|
||||||
* @param {Object | undefined} json JSON object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
JSONEditor.prototype._create = function (container, options, json) {
|
|
||||||
this.container = container;
|
|
||||||
this.options = options || {};
|
|
||||||
this.json = json || {};
|
|
||||||
|
|
||||||
var mode = this.options.mode || 'tree';
|
/**
|
||||||
this.setMode(mode);
|
* Configuration for all registered modes. Example:
|
||||||
};
|
* {
|
||||||
|
* tree: {
|
||||||
|
* mixin: TreeEditor,
|
||||||
|
* data: 'json'
|
||||||
|
* },
|
||||||
|
* text: {
|
||||||
|
* mixin: TextEditor,
|
||||||
|
* data: 'text'
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @type { Object.<String, {mixin: Object, data: String} > }
|
||||||
|
*/
|
||||||
|
JSONEditor.modes = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach the editor from the DOM
|
* Create the JSONEditor
|
||||||
* @private
|
* @param {Element} container Container element
|
||||||
*/
|
* @param {Object} [options] See description in constructor
|
||||||
JSONEditor.prototype._delete = function () {};
|
* @param {Object | undefined} json JSON object
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
JSONEditor.prototype._create = function (container, options, json) {
|
||||||
|
this.container = container;
|
||||||
|
this.options = options || {};
|
||||||
|
this.json = json || {};
|
||||||
|
|
||||||
/**
|
var mode = this.options.mode || 'tree';
|
||||||
* Set JSON object in editor
|
this.setMode(mode);
|
||||||
* @param {Object | undefined} json JSON data
|
};
|
||||||
*/
|
|
||||||
JSONEditor.prototype.set = function (json) {
|
|
||||||
this.json = json;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get JSON from the editor
|
* Detach the editor from the DOM
|
||||||
* @returns {Object} json
|
* @private
|
||||||
*/
|
*/
|
||||||
JSONEditor.prototype.get = function () {
|
JSONEditor.prototype._delete = function () {};
|
||||||
return this.json;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set string containing JSON for the editor
|
* Set JSON object in editor
|
||||||
* @param {String | undefined} jsonText
|
* @param {Object | undefined} json JSON data
|
||||||
*/
|
*/
|
||||||
JSONEditor.prototype.setText = function (jsonText) {
|
JSONEditor.prototype.set = function (json) {
|
||||||
this.json = util.parse(jsonText);
|
this.json = json;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get stringified JSON contents from the editor
|
* Get JSON from the editor
|
||||||
* @returns {String} jsonText
|
* @returns {Object} json
|
||||||
*/
|
*/
|
||||||
JSONEditor.prototype.getText = function () {
|
JSONEditor.prototype.get = function () {
|
||||||
return JSON.stringify(this.json);
|
return this.json;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a field name for the root node.
|
* Set string containing JSON for the editor
|
||||||
* @param {String | undefined} name
|
* @param {String | undefined} jsonText
|
||||||
*/
|
*/
|
||||||
JSONEditor.prototype.setName = function (name) {
|
JSONEditor.prototype.setText = function (jsonText) {
|
||||||
if (!this.options) {
|
this.json = util.parse(jsonText);
|
||||||
this.options = {};
|
};
|
||||||
}
|
|
||||||
this.options.name = name;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the field name for the root node.
|
* Get stringified JSON contents from the editor
|
||||||
* @return {String | undefined} name
|
* @returns {String} jsonText
|
||||||
*/
|
*/
|
||||||
JSONEditor.prototype.getName = function () {
|
JSONEditor.prototype.getText = function () {
|
||||||
return this.options && this.options.name;
|
return JSON.stringify(this.json);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the mode of the editor.
|
* Set a field name for the root node.
|
||||||
* JSONEditor will be extended with all methods needed for the chosen mode.
|
* @param {String | undefined} name
|
||||||
* @param {String} mode Available modes: 'tree' (default), 'view', 'form',
|
*/
|
||||||
* 'text', and 'code'.
|
JSONEditor.prototype.setName = function (name) {
|
||||||
*/
|
if (!this.options) {
|
||||||
JSONEditor.prototype.setMode = function (mode) {
|
this.options = {};
|
||||||
var container = this.container,
|
}
|
||||||
options = util.extend({}, this.options),
|
this.options.name = name;
|
||||||
data,
|
};
|
||||||
name;
|
|
||||||
|
|
||||||
options.mode = mode;
|
/**
|
||||||
var config = JSONEditor.modes[mode];
|
* Get the field name for the root node.
|
||||||
if (config) {
|
* @return {String | undefined} name
|
||||||
try {
|
*/
|
||||||
var asText = (config.data == 'text');
|
JSONEditor.prototype.getName = function () {
|
||||||
name = this.getName();
|
return this.options && this.options.name;
|
||||||
data = this[asText ? 'getText' : 'get'](); // get text or json
|
};
|
||||||
|
|
||||||
this._delete();
|
/**
|
||||||
util.clear(this);
|
* Change the mode of the editor.
|
||||||
util.extend(this, config.mixin);
|
* JSONEditor will be extended with all methods needed for the chosen mode.
|
||||||
this.create(container, options);
|
* @param {String} mode Available modes: 'tree' (default), 'view', 'form',
|
||||||
|
* 'text', and 'code'.
|
||||||
|
*/
|
||||||
|
JSONEditor.prototype.setMode = function (mode) {
|
||||||
|
var container = this.container,
|
||||||
|
options = util.extend({}, this.options),
|
||||||
|
data,
|
||||||
|
name;
|
||||||
|
|
||||||
this.setName(name);
|
options.mode = mode;
|
||||||
this[asText ? 'setText' : 'set'](data); // set text or json
|
var config = JSONEditor.modes[mode];
|
||||||
|
if (config) {
|
||||||
|
try {
|
||||||
|
var asText = (config.data == 'text');
|
||||||
|
name = this.getName();
|
||||||
|
data = this[asText ? 'getText' : 'get'](); // get text or json
|
||||||
|
|
||||||
if (typeof config.load === 'function') {
|
this._delete();
|
||||||
try {
|
util.clear(this);
|
||||||
config.load.call(this);
|
util.extend(this, config.mixin);
|
||||||
}
|
this.create(container, options);
|
||||||
catch (err) {}
|
|
||||||
|
this.setName(name);
|
||||||
|
this[asText ? 'setText' : 'set'](data); // set text or json
|
||||||
|
|
||||||
|
if (typeof config.load === 'function') {
|
||||||
|
try {
|
||||||
|
config.load.call(this);
|
||||||
}
|
}
|
||||||
}
|
catch (err) {}
|
||||||
catch (err) {
|
|
||||||
this._onError(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
catch (err) {
|
||||||
throw new Error('Unknown mode "' + options.mode + '"');
|
this._onError(err);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
else {
|
||||||
|
throw new Error('Unknown mode "' + options.mode + '"');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throw an error. If an error callback is configured in options.error, this
|
* Throw an error. If an error callback is configured in options.error, this
|
||||||
* callback will be invoked. Else, a regular error is thrown.
|
* callback will be invoked. Else, a regular error is thrown.
|
||||||
* @param {Error} err
|
* @param {Error} err
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
JSONEditor.prototype._onError = function(err) {
|
JSONEditor.prototype._onError = function(err) {
|
||||||
// TODO: onError is deprecated since version 2.2.0. cleanup some day
|
// TODO: onError is deprecated since version 2.2.0. cleanup some day
|
||||||
if (typeof this.onError === 'function') {
|
if (typeof this.onError === 'function') {
|
||||||
util.log('WARNING: JSONEditor.onError is deprecated. ' +
|
util.log('WARNING: JSONEditor.onError is deprecated. ' +
|
||||||
'Use options.error instead.');
|
'Use options.error instead.');
|
||||||
this.onError(err);
|
this.onError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options && typeof this.options.error === 'function') {
|
||||||
|
this.options.error(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a plugin with one ore multiple modes for the JSON Editor.
|
||||||
|
*
|
||||||
|
* A mode is described as an object with properties:
|
||||||
|
*
|
||||||
|
* - `mode: String` The name of the mode.
|
||||||
|
* - `mixin: Object` An object containing the mixin functions which
|
||||||
|
* will be added to the JSONEditor. Must contain functions
|
||||||
|
* create, get, getText, set, and setText. May have
|
||||||
|
* additional functions.
|
||||||
|
* When the JSONEditor switches to a mixin, all mixin
|
||||||
|
* functions are added to the JSONEditor, and then
|
||||||
|
* the function `create(container, options)` is executed.
|
||||||
|
* - `data: 'text' | 'json'` The type of data that will be used to load the mixin.
|
||||||
|
* - `[load: function]` An optional function called after the mixin
|
||||||
|
* has been loaded.
|
||||||
|
*
|
||||||
|
* @param {Object | Array} mode A mode object or an array with multiple mode objects.
|
||||||
|
*/
|
||||||
|
JSONEditor.registerMode = function (mode) {
|
||||||
|
var i, prop;
|
||||||
|
|
||||||
|
if (util.isArray(mode)) {
|
||||||
|
// multiple modes
|
||||||
|
for (i = 0; i < mode.length; i++) {
|
||||||
|
JSONEditor.registerMode(mode[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// validate the new mode
|
||||||
|
if (!('mode' in mode)) throw new Error('Property "mode" missing');
|
||||||
|
if (!('mixin' in mode)) throw new Error('Property "mixin" missing');
|
||||||
|
if (!('data' in mode)) throw new Error('Property "data" missing');
|
||||||
|
var name = mode.mode;
|
||||||
|
if (name in JSONEditor.modes) {
|
||||||
|
throw new Error('Mode "' + name + '" already registered');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options && typeof this.options.error === 'function') {
|
// validate the mixin
|
||||||
this.options.error(err);
|
if (typeof mode.mixin.create !== 'function') {
|
||||||
|
throw new Error('Required function "create" missing on mixin');
|
||||||
}
|
}
|
||||||
else {
|
var reserved = ['setMode', 'registerMode', 'modes'];
|
||||||
throw err;
|
for (i = 0; i < reserved.length; i++) {
|
||||||
}
|
prop = reserved[i];
|
||||||
};
|
if (prop in mode.mixin) {
|
||||||
|
throw new Error('Reserved property "' + prop + '" not allowed in mixin');
|
||||||
/**
|
|
||||||
* Register a plugin with one ore multiple modes for the JSON Editor.
|
|
||||||
*
|
|
||||||
* A mode is described as an object with properties:
|
|
||||||
*
|
|
||||||
* - `mode: String` The name of the mode.
|
|
||||||
* - `mixin: Object` An object containing the mixin functions which
|
|
||||||
* will be added to the JSONEditor. Must contain functions
|
|
||||||
* create, get, getText, set, and setText. May have
|
|
||||||
* additional functions.
|
|
||||||
* When the JSONEditor switches to a mixin, all mixin
|
|
||||||
* functions are added to the JSONEditor, and then
|
|
||||||
* the function `create(container, options)` is executed.
|
|
||||||
* - `data: 'text' | 'json'` The type of data that will be used to load the mixin.
|
|
||||||
* - `[load: function]` An optional function called after the mixin
|
|
||||||
* has been loaded.
|
|
||||||
*
|
|
||||||
* @param {Object | Array} mode A mode object or an array with multiple mode objects.
|
|
||||||
*/
|
|
||||||
JSONEditor.registerMode = function (mode) {
|
|
||||||
var i, prop;
|
|
||||||
|
|
||||||
if (util.isArray(mode)) {
|
|
||||||
// multiple modes
|
|
||||||
for (i = 0; i < mode.length; i++) {
|
|
||||||
JSONEditor.registerMode(mode[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// validate the new mode
|
|
||||||
if (!('mode' in mode)) throw new Error('Property "mode" missing');
|
|
||||||
if (!('mixin' in mode)) throw new Error('Property "mixin" missing');
|
|
||||||
if (!('data' in mode)) throw new Error('Property "data" missing');
|
|
||||||
var name = mode.mode;
|
|
||||||
if (name in JSONEditor.modes) {
|
|
||||||
throw new Error('Mode "' + name + '" already registered');
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate the mixin
|
JSONEditor.modes[name] = mode;
|
||||||
if (typeof mode.mixin.create !== 'function') {
|
}
|
||||||
throw new Error('Required function "create" missing on mixin');
|
};
|
||||||
}
|
|
||||||
var reserved = ['setMode', 'registerMode', 'modes'];
|
|
||||||
for (i = 0; i < reserved.length; i++) {
|
|
||||||
prop = reserved[i];
|
|
||||||
if (prop in mode.mixin) {
|
|
||||||
throw new Error('Reserved property "' + prop + '" not allowed in mixin');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONEditor.modes[name] = mode;
|
// register tree and text modes
|
||||||
}
|
JSONEditor.registerMode(treemode);
|
||||||
};
|
JSONEditor.registerMode(textmode);
|
||||||
|
|
||||||
// register tree and text modes
|
module.exports = JSONEditor;
|
||||||
JSONEditor.registerMode(treemode);
|
|
||||||
JSONEditor.registerMode(textmode);
|
|
||||||
|
|
||||||
return JSONEditor;
|
|
||||||
});
|
|
||||||
|
|
5425
src/js/Node.js
5425
src/js/Node.js
File diff suppressed because it is too large
Load Diff
|
@ -1,293 +1,288 @@
|
||||||
define(function () {
|
/**
|
||||||
|
* @constructor SearchBox
|
||||||
|
* Create a search box in given HTML container
|
||||||
|
* @param {JSONEditor} editor The JSON Editor to attach to
|
||||||
|
* @param {Element} container HTML container element of where to
|
||||||
|
* create the search box
|
||||||
|
*/
|
||||||
|
function SearchBox (editor, container) {
|
||||||
|
var searchBox = this;
|
||||||
|
|
||||||
/**
|
this.editor = editor;
|
||||||
* @constructor SearchBox
|
this.timeout = undefined;
|
||||||
* Create a search box in given HTML container
|
this.delay = 200; // ms
|
||||||
* @param {JSONEditor} editor The JSON Editor to attach to
|
this.lastText = undefined;
|
||||||
* @param {Element} container HTML container element of where to
|
|
||||||
* create the search box
|
|
||||||
*/
|
|
||||||
function SearchBox (editor, container) {
|
|
||||||
var searchBox = this;
|
|
||||||
|
|
||||||
this.editor = editor;
|
this.dom = {};
|
||||||
this.timeout = undefined;
|
this.dom.container = container;
|
||||||
this.delay = 200; // ms
|
|
||||||
this.lastText = undefined;
|
|
||||||
|
|
||||||
this.dom = {};
|
var table = document.createElement('table');
|
||||||
this.dom.container = container;
|
this.dom.table = table;
|
||||||
|
table.className = 'search';
|
||||||
|
container.appendChild(table);
|
||||||
|
var tbody = document.createElement('tbody');
|
||||||
|
this.dom.tbody = tbody;
|
||||||
|
table.appendChild(tbody);
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
|
||||||
var table = document.createElement('table');
|
var td = document.createElement('td');
|
||||||
this.dom.table = table;
|
tr.appendChild(td);
|
||||||
table.className = 'search';
|
var results = document.createElement('div');
|
||||||
container.appendChild(table);
|
this.dom.results = results;
|
||||||
var tbody = document.createElement('tbody');
|
results.className = 'results';
|
||||||
this.dom.tbody = tbody;
|
td.appendChild(results);
|
||||||
table.appendChild(tbody);
|
|
||||||
var tr = document.createElement('tr');
|
|
||||||
tbody.appendChild(tr);
|
|
||||||
|
|
||||||
var td = document.createElement('td');
|
td = document.createElement('td');
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
var results = document.createElement('div');
|
var divInput = document.createElement('div');
|
||||||
this.dom.results = results;
|
this.dom.input = divInput;
|
||||||
results.className = 'results';
|
divInput.className = 'frame';
|
||||||
td.appendChild(results);
|
divInput.title = 'Search fields and values';
|
||||||
|
td.appendChild(divInput);
|
||||||
|
|
||||||
td = document.createElement('td');
|
// table to contain the text input and search button
|
||||||
tr.appendChild(td);
|
var tableInput = document.createElement('table');
|
||||||
var divInput = document.createElement('div');
|
divInput.appendChild(tableInput);
|
||||||
this.dom.input = divInput;
|
var tbodySearch = document.createElement('tbody');
|
||||||
divInput.className = 'frame';
|
tableInput.appendChild(tbodySearch);
|
||||||
divInput.title = 'Search fields and values';
|
tr = document.createElement('tr');
|
||||||
td.appendChild(divInput);
|
tbodySearch.appendChild(tr);
|
||||||
|
|
||||||
// table to contain the text input and search button
|
var refreshSearch = document.createElement('button');
|
||||||
var tableInput = document.createElement('table');
|
refreshSearch.className = 'refresh';
|
||||||
divInput.appendChild(tableInput);
|
td = document.createElement('td');
|
||||||
var tbodySearch = document.createElement('tbody');
|
td.appendChild(refreshSearch);
|
||||||
tableInput.appendChild(tbodySearch);
|
tr.appendChild(td);
|
||||||
tr = document.createElement('tr');
|
|
||||||
tbodySearch.appendChild(tr);
|
|
||||||
|
|
||||||
var refreshSearch = document.createElement('button');
|
var search = document.createElement('input');
|
||||||
refreshSearch.className = 'refresh';
|
this.dom.search = search;
|
||||||
td = document.createElement('td');
|
search.oninput = function (event) {
|
||||||
td.appendChild(refreshSearch);
|
searchBox._onDelayedSearch(event);
|
||||||
tr.appendChild(td);
|
};
|
||||||
|
search.onchange = function (event) { // For IE 9
|
||||||
|
searchBox._onSearch(event);
|
||||||
|
};
|
||||||
|
search.onkeydown = function (event) {
|
||||||
|
searchBox._onKeyDown(event);
|
||||||
|
};
|
||||||
|
search.onkeyup = function (event) {
|
||||||
|
searchBox._onKeyUp(event);
|
||||||
|
};
|
||||||
|
refreshSearch.onclick = function (event) {
|
||||||
|
search.select();
|
||||||
|
};
|
||||||
|
|
||||||
var search = document.createElement('input');
|
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
|
||||||
this.dom.search = search;
|
td = document.createElement('td');
|
||||||
search.oninput = function (event) {
|
td.appendChild(search);
|
||||||
searchBox._onDelayedSearch(event);
|
tr.appendChild(td);
|
||||||
};
|
|
||||||
search.onchange = function (event) { // For IE 9
|
|
||||||
searchBox._onSearch(event);
|
|
||||||
};
|
|
||||||
search.onkeydown = function (event) {
|
|
||||||
searchBox._onKeyDown(event);
|
|
||||||
};
|
|
||||||
search.onkeyup = function (event) {
|
|
||||||
searchBox._onKeyUp(event);
|
|
||||||
};
|
|
||||||
refreshSearch.onclick = function (event) {
|
|
||||||
search.select();
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
|
var searchNext = document.createElement('button');
|
||||||
td = document.createElement('td');
|
searchNext.title = 'Next result (Enter)';
|
||||||
td.appendChild(search);
|
searchNext.className = 'next';
|
||||||
tr.appendChild(td);
|
searchNext.onclick = function () {
|
||||||
|
searchBox.next();
|
||||||
|
};
|
||||||
|
td = document.createElement('td');
|
||||||
|
td.appendChild(searchNext);
|
||||||
|
tr.appendChild(td);
|
||||||
|
|
||||||
var searchNext = document.createElement('button');
|
var searchPrevious = document.createElement('button');
|
||||||
searchNext.title = 'Next result (Enter)';
|
searchPrevious.title = 'Previous result (Shift+Enter)';
|
||||||
searchNext.className = 'next';
|
searchPrevious.className = 'previous';
|
||||||
searchNext.onclick = function () {
|
searchPrevious.onclick = function () {
|
||||||
searchBox.next();
|
searchBox.previous();
|
||||||
};
|
};
|
||||||
td = document.createElement('td');
|
td = document.createElement('td');
|
||||||
td.appendChild(searchNext);
|
td.appendChild(searchPrevious);
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
|
||||||
var searchPrevious = document.createElement('button');
|
/**
|
||||||
searchPrevious.title = 'Previous result (Shift+Enter)';
|
* Go to the next search result
|
||||||
searchPrevious.className = 'previous';
|
* @param {boolean} [focus] If true, focus will be set to the next result
|
||||||
searchPrevious.onclick = function () {
|
* focus is false by default.
|
||||||
searchBox.previous();
|
*/
|
||||||
};
|
SearchBox.prototype.next = function(focus) {
|
||||||
td = document.createElement('td');
|
if (this.results != undefined) {
|
||||||
td.appendChild(searchPrevious);
|
var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0;
|
||||||
tr.appendChild(td);
|
if (index > this.results.length - 1) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
this._setActiveResult(index, focus);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to the next search result
|
* Go to the prevous search result
|
||||||
* @param {boolean} [focus] If true, focus will be set to the next result
|
* @param {boolean} [focus] If true, focus will be set to the next result
|
||||||
* focus is false by default.
|
* focus is false by default.
|
||||||
*/
|
*/
|
||||||
SearchBox.prototype.next = function(focus) {
|
SearchBox.prototype.previous = function(focus) {
|
||||||
if (this.results != undefined) {
|
if (this.results != undefined) {
|
||||||
var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0;
|
var max = this.results.length - 1;
|
||||||
if (index > this.results.length - 1) {
|
var index = (this.resultIndex != undefined) ? this.resultIndex - 1 : max;
|
||||||
index = 0;
|
if (index < 0) {
|
||||||
}
|
index = max;
|
||||||
this._setActiveResult(index, focus);
|
|
||||||
}
|
}
|
||||||
};
|
this._setActiveResult(index, focus);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to the prevous search result
|
* Set new value for the current active result
|
||||||
* @param {boolean} [focus] If true, focus will be set to the next result
|
* @param {Number} index
|
||||||
* focus is false by default.
|
* @param {boolean} [focus] If true, focus will be set to the next result.
|
||||||
*/
|
* focus is false by default.
|
||||||
SearchBox.prototype.previous = function(focus) {
|
* @private
|
||||||
if (this.results != undefined) {
|
*/
|
||||||
var max = this.results.length - 1;
|
SearchBox.prototype._setActiveResult = function(index, focus) {
|
||||||
var index = (this.resultIndex != undefined) ? this.resultIndex - 1 : max;
|
// de-activate current active result
|
||||||
if (index < 0) {
|
if (this.activeResult) {
|
||||||
index = max;
|
var prevNode = this.activeResult.node;
|
||||||
}
|
var prevElem = this.activeResult.elem;
|
||||||
this._setActiveResult(index, focus);
|
if (prevElem == 'field') {
|
||||||
}
|
delete prevNode.searchFieldActive;
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set new value for the current active result
|
|
||||||
* @param {Number} index
|
|
||||||
* @param {boolean} [focus] If true, focus will be set to the next result.
|
|
||||||
* focus is false by default.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
SearchBox.prototype._setActiveResult = function(index, focus) {
|
|
||||||
// de-activate current active result
|
|
||||||
if (this.activeResult) {
|
|
||||||
var prevNode = this.activeResult.node;
|
|
||||||
var prevElem = this.activeResult.elem;
|
|
||||||
if (prevElem == 'field') {
|
|
||||||
delete prevNode.searchFieldActive;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
delete prevNode.searchValueActive;
|
|
||||||
}
|
|
||||||
prevNode.updateDom();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.results || !this.results[index]) {
|
|
||||||
// out of range, set to undefined
|
|
||||||
this.resultIndex = undefined;
|
|
||||||
this.activeResult = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resultIndex = index;
|
|
||||||
|
|
||||||
// set new node active
|
|
||||||
var node = this.results[this.resultIndex].node;
|
|
||||||
var elem = this.results[this.resultIndex].elem;
|
|
||||||
if (elem == 'field') {
|
|
||||||
node.searchFieldActive = true;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.searchValueActive = true;
|
delete prevNode.searchValueActive;
|
||||||
}
|
}
|
||||||
this.activeResult = this.results[this.resultIndex];
|
prevNode.updateDom();
|
||||||
node.updateDom();
|
}
|
||||||
|
|
||||||
// TODO: not so nice that the focus is only set after the animation is finished
|
if (!this.results || !this.results[index]) {
|
||||||
node.scrollTo(function () {
|
// out of range, set to undefined
|
||||||
if (focus) {
|
this.resultIndex = undefined;
|
||||||
node.focus(elem);
|
this.activeResult = undefined;
|
||||||
}
|
return;
|
||||||
});
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
this.resultIndex = index;
|
||||||
* Cancel any running onDelayedSearch.
|
|
||||||
* @private
|
// set new node active
|
||||||
*/
|
var node = this.results[this.resultIndex].node;
|
||||||
SearchBox.prototype._clearDelay = function() {
|
var elem = this.results[this.resultIndex].elem;
|
||||||
if (this.timeout != undefined) {
|
if (elem == 'field') {
|
||||||
clearTimeout(this.timeout);
|
node.searchFieldActive = true;
|
||||||
delete this.timeout;
|
}
|
||||||
|
else {
|
||||||
|
node.searchValueActive = true;
|
||||||
|
}
|
||||||
|
this.activeResult = this.results[this.resultIndex];
|
||||||
|
node.updateDom();
|
||||||
|
|
||||||
|
// TODO: not so nice that the focus is only set after the animation is finished
|
||||||
|
node.scrollTo(function () {
|
||||||
|
if (focus) {
|
||||||
|
node.focus(elem);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a timer to execute a search after a short delay.
|
* Cancel any running onDelayedSearch.
|
||||||
* Used for reducing the number of searches while typing.
|
* @private
|
||||||
* @param {Event} event
|
*/
|
||||||
* @private
|
SearchBox.prototype._clearDelay = function() {
|
||||||
*/
|
if (this.timeout != undefined) {
|
||||||
SearchBox.prototype._onDelayedSearch = function (event) {
|
clearTimeout(this.timeout);
|
||||||
// execute the search after a short delay (reduces the number of
|
delete this.timeout;
|
||||||
// search actions while typing in the search text box)
|
}
|
||||||
this._clearDelay();
|
};
|
||||||
var searchBox = this;
|
|
||||||
this.timeout = setTimeout(function (event) {
|
|
||||||
searchBox._onSearch(event);
|
|
||||||
},
|
|
||||||
this.delay);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle onSearch event
|
* Start a timer to execute a search after a short delay.
|
||||||
* @param {Event} event
|
* Used for reducing the number of searches while typing.
|
||||||
* @param {boolean} [forceSearch] If true, search will be executed again even
|
* @param {Event} event
|
||||||
* when the search text is not changed.
|
* @private
|
||||||
* Default is false.
|
*/
|
||||||
* @private
|
SearchBox.prototype._onDelayedSearch = function (event) {
|
||||||
*/
|
// execute the search after a short delay (reduces the number of
|
||||||
SearchBox.prototype._onSearch = function (event, forceSearch) {
|
// search actions while typing in the search text box)
|
||||||
this._clearDelay();
|
this._clearDelay();
|
||||||
|
var searchBox = this;
|
||||||
|
this.timeout = setTimeout(function (event) {
|
||||||
|
searchBox._onSearch(event);
|
||||||
|
},
|
||||||
|
this.delay);
|
||||||
|
};
|
||||||
|
|
||||||
var value = this.dom.search.value;
|
/**
|
||||||
var text = (value.length > 0) ? value : undefined;
|
* Handle onSearch event
|
||||||
if (text != this.lastText || forceSearch) {
|
* @param {Event} event
|
||||||
// only search again when changed
|
* @param {boolean} [forceSearch] If true, search will be executed again even
|
||||||
this.lastText = text;
|
* when the search text is not changed.
|
||||||
this.results = this.editor.search(text);
|
* Default is false.
|
||||||
this._setActiveResult(undefined);
|
* @private
|
||||||
|
*/
|
||||||
|
SearchBox.prototype._onSearch = function (event, forceSearch) {
|
||||||
|
this._clearDelay();
|
||||||
|
|
||||||
// display search results
|
var value = this.dom.search.value;
|
||||||
if (text != undefined) {
|
var text = (value.length > 0) ? value : undefined;
|
||||||
var resultCount = this.results.length;
|
if (text != this.lastText || forceSearch) {
|
||||||
switch (resultCount) {
|
// only search again when changed
|
||||||
case 0: this.dom.results.innerHTML = 'no results'; break;
|
this.lastText = text;
|
||||||
case 1: this.dom.results.innerHTML = '1 result'; break;
|
this.results = this.editor.search(text);
|
||||||
default: this.dom.results.innerHTML = resultCount + ' results'; break;
|
this._setActiveResult(undefined);
|
||||||
}
|
|
||||||
}
|
// display search results
|
||||||
else {
|
if (text != undefined) {
|
||||||
this.dom.results.innerHTML = '';
|
var resultCount = this.results.length;
|
||||||
|
switch (resultCount) {
|
||||||
|
case 0: this.dom.results.innerHTML = 'no results'; break;
|
||||||
|
case 1: this.dom.results.innerHTML = '1 result'; break;
|
||||||
|
default: this.dom.results.innerHTML = resultCount + ' results'; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
else {
|
||||||
|
this.dom.results.innerHTML = '';
|
||||||
/**
|
|
||||||
* Handle onKeyDown event in the input box
|
|
||||||
* @param {Event} event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
SearchBox.prototype._onKeyDown = function (event) {
|
|
||||||
var keynum = event.which;
|
|
||||||
if (keynum == 27) { // ESC
|
|
||||||
this.dom.search.value = ''; // clear search
|
|
||||||
this._onSearch(event);
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
else if (keynum == 13) { // Enter
|
}
|
||||||
if (event.ctrlKey) {
|
};
|
||||||
// force to search again
|
|
||||||
this._onSearch(event, true);
|
/**
|
||||||
}
|
* Handle onKeyDown event in the input box
|
||||||
else if (event.shiftKey) {
|
* @param {Event} event
|
||||||
// move to the previous search result
|
* @private
|
||||||
this.previous();
|
*/
|
||||||
}
|
SearchBox.prototype._onKeyDown = function (event) {
|
||||||
else {
|
var keynum = event.which;
|
||||||
// move to the next search result
|
if (keynum == 27) { // ESC
|
||||||
this.next();
|
this.dom.search.value = ''; // clear search
|
||||||
}
|
this._onSearch(event);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
else if (keynum == 13) { // Enter
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
// force to search again
|
||||||
|
this._onSearch(event, true);
|
||||||
}
|
}
|
||||||
};
|
else if (event.shiftKey) {
|
||||||
|
// move to the previous search result
|
||||||
/**
|
this.previous();
|
||||||
* Handle onKeyUp event in the input box
|
|
||||||
* @param {Event} event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
SearchBox.prototype._onKeyUp = function (event) {
|
|
||||||
var keynum = event.keyCode;
|
|
||||||
if (keynum != 27 && keynum != 13) { // !show and !Enter
|
|
||||||
this._onDelayedSearch(event); // For IE 9
|
|
||||||
}
|
}
|
||||||
};
|
else {
|
||||||
|
// move to the next search result
|
||||||
return SearchBox;
|
this.next();
|
||||||
});
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle onKeyUp event in the input box
|
||||||
|
* @param {Event} event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SearchBox.prototype._onKeyUp = function (event) {
|
||||||
|
var keynum = event.keyCode;
|
||||||
|
if (keynum != 27 && keynum != 13) { // !show and !Enter
|
||||||
|
this._onDelayedSearch(event); // For IE 9
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = SearchBox;
|
||||||
|
|
|
@ -1,227 +1,226 @@
|
||||||
define(['./ContextMenu', './util'], function (ContextMenu, util) {
|
var util = require('./util');
|
||||||
|
var ContextMenu = require('./ContextMenu');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory function to create an AppendNode, which depends on a Node
|
||||||
|
* @param {Node} Node
|
||||||
|
*/
|
||||||
|
function appendNodeFactory(Node) {
|
||||||
/**
|
/**
|
||||||
* A factory function to create an AppendNode, which depends on a Node
|
* @constructor AppendNode
|
||||||
* @param {Node} Node
|
* @extends Node
|
||||||
|
* @param {TreeEditor} editor
|
||||||
|
* Create a new AppendNode. This is a special node which is created at the
|
||||||
|
* end of the list with childs for an object or array
|
||||||
*/
|
*/
|
||||||
function appendNodeFactory(Node) {
|
function AppendNode (editor) {
|
||||||
/**
|
/** @type {TreeEditor} */
|
||||||
* @constructor AppendNode
|
this.editor = editor;
|
||||||
* @extends Node
|
this.dom = {};
|
||||||
* @param {TreeEditor} editor
|
|
||||||
* Create a new AppendNode. This is a special node which is created at the
|
|
||||||
* end of the list with childs for an object or array
|
|
||||||
*/
|
|
||||||
function AppendNode (editor) {
|
|
||||||
/** @type {TreeEditor} */
|
|
||||||
this.editor = editor;
|
|
||||||
this.dom = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
AppendNode.prototype = new Node();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a table row with an append button.
|
|
||||||
* @return {Element} dom TR element
|
|
||||||
*/
|
|
||||||
AppendNode.prototype.getDom = function () {
|
|
||||||
// TODO: implement a new solution for the append node
|
|
||||||
var dom = this.dom;
|
|
||||||
|
|
||||||
if (dom.tr) {
|
|
||||||
return dom.tr;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._updateEditability();
|
|
||||||
|
|
||||||
// a row for the append button
|
|
||||||
var trAppend = document.createElement('tr');
|
|
||||||
trAppend.node = this;
|
|
||||||
dom.tr = trAppend;
|
|
||||||
|
|
||||||
// TODO: consistent naming
|
|
||||||
|
|
||||||
if (this.editable.field) {
|
|
||||||
// a cell for the dragarea column
|
|
||||||
dom.tdDrag = document.createElement('td');
|
|
||||||
|
|
||||||
// create context menu
|
|
||||||
var tdMenu = document.createElement('td');
|
|
||||||
dom.tdMenu = tdMenu;
|
|
||||||
var menu = document.createElement('button');
|
|
||||||
menu.className = 'contextmenu';
|
|
||||||
menu.title = 'Click to open the actions menu (Ctrl+M)';
|
|
||||||
dom.menu = menu;
|
|
||||||
tdMenu.appendChild(dom.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
// a cell for the contents (showing text 'empty')
|
|
||||||
var tdAppend = document.createElement('td');
|
|
||||||
var domText = document.createElement('div');
|
|
||||||
domText.innerHTML = '(empty)';
|
|
||||||
domText.className = 'readonly';
|
|
||||||
tdAppend.appendChild(domText);
|
|
||||||
dom.td = tdAppend;
|
|
||||||
dom.text = domText;
|
|
||||||
|
|
||||||
this.updateDom();
|
|
||||||
|
|
||||||
return trAppend;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the HTML dom of the Node
|
|
||||||
*/
|
|
||||||
AppendNode.prototype.updateDom = function () {
|
|
||||||
var dom = this.dom;
|
|
||||||
var tdAppend = dom.td;
|
|
||||||
if (tdAppend) {
|
|
||||||
tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px';
|
|
||||||
// TODO: not so nice hard coded offset
|
|
||||||
}
|
|
||||||
|
|
||||||
var domText = dom.text;
|
|
||||||
if (domText) {
|
|
||||||
domText.innerHTML = '(empty ' + this.parent.type + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
// attach or detach the contents of the append node:
|
|
||||||
// hide when the parent has childs, show when the parent has no childs
|
|
||||||
var trAppend = dom.tr;
|
|
||||||
if (!this.isVisible()) {
|
|
||||||
if (dom.tr.firstChild) {
|
|
||||||
if (dom.tdDrag) {
|
|
||||||
trAppend.removeChild(dom.tdDrag);
|
|
||||||
}
|
|
||||||
if (dom.tdMenu) {
|
|
||||||
trAppend.removeChild(dom.tdMenu);
|
|
||||||
}
|
|
||||||
trAppend.removeChild(tdAppend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!dom.tr.firstChild) {
|
|
||||||
if (dom.tdDrag) {
|
|
||||||
trAppend.appendChild(dom.tdDrag);
|
|
||||||
}
|
|
||||||
if (dom.tdMenu) {
|
|
||||||
trAppend.appendChild(dom.tdMenu);
|
|
||||||
}
|
|
||||||
trAppend.appendChild(tdAppend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the AppendNode is currently visible.
|
|
||||||
* the AppendNode is visible when its parent has no childs (i.e. is empty).
|
|
||||||
* @return {boolean} isVisible
|
|
||||||
*/
|
|
||||||
AppendNode.prototype.isVisible = function () {
|
|
||||||
return (this.parent.childs.length == 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a contextmenu for this node
|
|
||||||
* @param {HTMLElement} anchor The element to attach the menu to.
|
|
||||||
* @param {function} [onClose] Callback method called when the context menu
|
|
||||||
* is being closed.
|
|
||||||
*/
|
|
||||||
AppendNode.prototype.showContextMenu = function (anchor, onClose) {
|
|
||||||
var node = this;
|
|
||||||
var titles = Node.TYPE_TITLES;
|
|
||||||
var items = [
|
|
||||||
// create append button
|
|
||||||
{
|
|
||||||
'text': 'Append',
|
|
||||||
'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)',
|
|
||||||
'submenuTitle': 'Select the type of the field to be appended',
|
|
||||||
'className': 'insert',
|
|
||||||
'click': function () {
|
|
||||||
node._onAppend('', '', 'auto');
|
|
||||||
},
|
|
||||||
'submenu': [
|
|
||||||
{
|
|
||||||
'text': 'Auto',
|
|
||||||
'className': 'type-auto',
|
|
||||||
'title': titles.auto,
|
|
||||||
'click': function () {
|
|
||||||
node._onAppend('', '', 'auto');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'text': 'Array',
|
|
||||||
'className': 'type-array',
|
|
||||||
'title': titles.array,
|
|
||||||
'click': function () {
|
|
||||||
node._onAppend('', []);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'text': 'Object',
|
|
||||||
'className': 'type-object',
|
|
||||||
'title': titles.object,
|
|
||||||
'click': function () {
|
|
||||||
node._onAppend('', {});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'text': 'String',
|
|
||||||
'className': 'type-string',
|
|
||||||
'title': titles.string,
|
|
||||||
'click': function () {
|
|
||||||
node._onAppend('', '', 'string');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
var menu = new ContextMenu(items, {close: onClose});
|
|
||||||
menu.show(anchor);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an event. The event is catched centrally by the editor
|
|
||||||
* @param {Event} event
|
|
||||||
*/
|
|
||||||
AppendNode.prototype.onEvent = function (event) {
|
|
||||||
var type = event.type;
|
|
||||||
var target = event.target || event.srcElement;
|
|
||||||
var dom = this.dom;
|
|
||||||
|
|
||||||
// highlight the append nodes parent
|
|
||||||
var menu = dom.menu;
|
|
||||||
if (target == menu) {
|
|
||||||
if (type == 'mouseover') {
|
|
||||||
this.editor.highlighter.highlight(this.parent);
|
|
||||||
}
|
|
||||||
else if (type == 'mouseout') {
|
|
||||||
this.editor.highlighter.unhighlight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// context menu events
|
|
||||||
if (type == 'click' && target == dom.menu) {
|
|
||||||
var highlighter = this.editor.highlighter;
|
|
||||||
highlighter.highlight(this.parent);
|
|
||||||
highlighter.lock();
|
|
||||||
util.addClassName(dom.menu, 'selected');
|
|
||||||
this.showContextMenu(dom.menu, function () {
|
|
||||||
util.removeClassName(dom.menu, 'selected');
|
|
||||||
highlighter.unlock();
|
|
||||||
highlighter.unhighlight();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == 'keydown') {
|
|
||||||
this.onKeyDown(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return AppendNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the factory function
|
AppendNode.prototype = new Node();
|
||||||
return appendNodeFactory;
|
|
||||||
});
|
/**
|
||||||
|
* Return a table row with an append button.
|
||||||
|
* @return {Element} dom TR element
|
||||||
|
*/
|
||||||
|
AppendNode.prototype.getDom = function () {
|
||||||
|
// TODO: implement a new solution for the append node
|
||||||
|
var dom = this.dom;
|
||||||
|
|
||||||
|
if (dom.tr) {
|
||||||
|
return dom.tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateEditability();
|
||||||
|
|
||||||
|
// a row for the append button
|
||||||
|
var trAppend = document.createElement('tr');
|
||||||
|
trAppend.node = this;
|
||||||
|
dom.tr = trAppend;
|
||||||
|
|
||||||
|
// TODO: consistent naming
|
||||||
|
|
||||||
|
if (this.editable.field) {
|
||||||
|
// a cell for the dragarea column
|
||||||
|
dom.tdDrag = document.createElement('td');
|
||||||
|
|
||||||
|
// create context menu
|
||||||
|
var tdMenu = document.createElement('td');
|
||||||
|
dom.tdMenu = tdMenu;
|
||||||
|
var menu = document.createElement('button');
|
||||||
|
menu.className = 'contextmenu';
|
||||||
|
menu.title = 'Click to open the actions menu (Ctrl+M)';
|
||||||
|
dom.menu = menu;
|
||||||
|
tdMenu.appendChild(dom.menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a cell for the contents (showing text 'empty')
|
||||||
|
var tdAppend = document.createElement('td');
|
||||||
|
var domText = document.createElement('div');
|
||||||
|
domText.innerHTML = '(empty)';
|
||||||
|
domText.className = 'readonly';
|
||||||
|
tdAppend.appendChild(domText);
|
||||||
|
dom.td = tdAppend;
|
||||||
|
dom.text = domText;
|
||||||
|
|
||||||
|
this.updateDom();
|
||||||
|
|
||||||
|
return trAppend;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the HTML dom of the Node
|
||||||
|
*/
|
||||||
|
AppendNode.prototype.updateDom = function () {
|
||||||
|
var dom = this.dom;
|
||||||
|
var tdAppend = dom.td;
|
||||||
|
if (tdAppend) {
|
||||||
|
tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px';
|
||||||
|
// TODO: not so nice hard coded offset
|
||||||
|
}
|
||||||
|
|
||||||
|
var domText = dom.text;
|
||||||
|
if (domText) {
|
||||||
|
domText.innerHTML = '(empty ' + this.parent.type + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach or detach the contents of the append node:
|
||||||
|
// hide when the parent has childs, show when the parent has no childs
|
||||||
|
var trAppend = dom.tr;
|
||||||
|
if (!this.isVisible()) {
|
||||||
|
if (dom.tr.firstChild) {
|
||||||
|
if (dom.tdDrag) {
|
||||||
|
trAppend.removeChild(dom.tdDrag);
|
||||||
|
}
|
||||||
|
if (dom.tdMenu) {
|
||||||
|
trAppend.removeChild(dom.tdMenu);
|
||||||
|
}
|
||||||
|
trAppend.removeChild(tdAppend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!dom.tr.firstChild) {
|
||||||
|
if (dom.tdDrag) {
|
||||||
|
trAppend.appendChild(dom.tdDrag);
|
||||||
|
}
|
||||||
|
if (dom.tdMenu) {
|
||||||
|
trAppend.appendChild(dom.tdMenu);
|
||||||
|
}
|
||||||
|
trAppend.appendChild(tdAppend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the AppendNode is currently visible.
|
||||||
|
* the AppendNode is visible when its parent has no childs (i.e. is empty).
|
||||||
|
* @return {boolean} isVisible
|
||||||
|
*/
|
||||||
|
AppendNode.prototype.isVisible = function () {
|
||||||
|
return (this.parent.childs.length == 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a contextmenu for this node
|
||||||
|
* @param {HTMLElement} anchor The element to attach the menu to.
|
||||||
|
* @param {function} [onClose] Callback method called when the context menu
|
||||||
|
* is being closed.
|
||||||
|
*/
|
||||||
|
AppendNode.prototype.showContextMenu = function (anchor, onClose) {
|
||||||
|
var node = this;
|
||||||
|
var titles = Node.TYPE_TITLES;
|
||||||
|
var items = [
|
||||||
|
// create append button
|
||||||
|
{
|
||||||
|
'text': 'Append',
|
||||||
|
'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)',
|
||||||
|
'submenuTitle': 'Select the type of the field to be appended',
|
||||||
|
'className': 'insert',
|
||||||
|
'click': function () {
|
||||||
|
node._onAppend('', '', 'auto');
|
||||||
|
},
|
||||||
|
'submenu': [
|
||||||
|
{
|
||||||
|
'text': 'Auto',
|
||||||
|
'className': 'type-auto',
|
||||||
|
'title': titles.auto,
|
||||||
|
'click': function () {
|
||||||
|
node._onAppend('', '', 'auto');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text': 'Array',
|
||||||
|
'className': 'type-array',
|
||||||
|
'title': titles.array,
|
||||||
|
'click': function () {
|
||||||
|
node._onAppend('', []);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text': 'Object',
|
||||||
|
'className': 'type-object',
|
||||||
|
'title': titles.object,
|
||||||
|
'click': function () {
|
||||||
|
node._onAppend('', {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'text': 'String',
|
||||||
|
'className': 'type-string',
|
||||||
|
'title': titles.string,
|
||||||
|
'click': function () {
|
||||||
|
node._onAppend('', '', 'string');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var menu = new ContextMenu(items, {close: onClose});
|
||||||
|
menu.show(anchor);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an event. The event is catched centrally by the editor
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
AppendNode.prototype.onEvent = function (event) {
|
||||||
|
var type = event.type;
|
||||||
|
var target = event.target || event.srcElement;
|
||||||
|
var dom = this.dom;
|
||||||
|
|
||||||
|
// highlight the append nodes parent
|
||||||
|
var menu = dom.menu;
|
||||||
|
if (target == menu) {
|
||||||
|
if (type == 'mouseover') {
|
||||||
|
this.editor.highlighter.highlight(this.parent);
|
||||||
|
}
|
||||||
|
else if (type == 'mouseout') {
|
||||||
|
this.editor.highlighter.unhighlight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// context menu events
|
||||||
|
if (type == 'click' && target == dom.menu) {
|
||||||
|
var highlighter = this.editor.highlighter;
|
||||||
|
highlighter.highlight(this.parent);
|
||||||
|
highlighter.lock();
|
||||||
|
util.addClassName(dom.menu, 'selected');
|
||||||
|
this.showContextMenu(dom.menu, function () {
|
||||||
|
util.removeClassName(dom.menu, 'selected');
|
||||||
|
highlighter.unlock();
|
||||||
|
highlighter.unhighlight();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 'keydown') {
|
||||||
|
this.onKeyDown(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return AppendNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = appendNodeFactory;
|
||||||
|
|
|
@ -1,103 +1,100 @@
|
||||||
define(['./ContextMenu'], function (ContextMenu) {
|
var ContextMenu = require('./ContextMenu');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a select box to be used in the editor menu's, which allows to switch mode
|
||||||
|
* @param {Object} editor
|
||||||
|
* @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view'
|
||||||
|
* @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view'
|
||||||
|
* @returns {HTMLElement} box
|
||||||
|
*/
|
||||||
|
function createModeSwitcher(editor, modes, current) {
|
||||||
|
// TODO: decouple mode switcher from editor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a select box to be used in the editor menu's, which allows to switch mode
|
* Switch the mode of the editor
|
||||||
* @param {Object} editor
|
* @param {String} mode
|
||||||
* @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view'
|
|
||||||
* @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view'
|
|
||||||
* @returns {HTMLElement} box
|
|
||||||
*/
|
*/
|
||||||
function createModeSwitcher(editor, modes, current) {
|
function switchMode(mode) {
|
||||||
// TODO: decouple mode switcher from editor
|
// switch mode
|
||||||
|
editor.setMode(mode);
|
||||||
|
|
||||||
/**
|
// restore focus on mode box
|
||||||
* Switch the mode of the editor
|
var modeBox = editor.dom && editor.dom.modeBox;
|
||||||
* @param {String} mode
|
if (modeBox) {
|
||||||
*/
|
modeBox.focus();
|
||||||
function switchMode(mode) {
|
|
||||||
// switch mode
|
|
||||||
editor.setMode(mode);
|
|
||||||
|
|
||||||
// restore focus on mode box
|
|
||||||
var modeBox = editor.dom && editor.dom.modeBox;
|
|
||||||
if (modeBox) {
|
|
||||||
modeBox.focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// available modes
|
|
||||||
var availableModes = {
|
|
||||||
code: {
|
|
||||||
'text': 'Code',
|
|
||||||
'title': 'Switch to code highlighter',
|
|
||||||
'click': function () {
|
|
||||||
switchMode('code')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
'text': 'Form',
|
|
||||||
'title': 'Switch to form editor',
|
|
||||||
'click': function () {
|
|
||||||
switchMode('form');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
'text': 'Text',
|
|
||||||
'title': 'Switch to plain text editor',
|
|
||||||
'click': function () {
|
|
||||||
switchMode('text');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tree: {
|
|
||||||
'text': 'Tree',
|
|
||||||
'title': 'Switch to tree editor',
|
|
||||||
'click': function () {
|
|
||||||
switchMode('tree');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
view: {
|
|
||||||
'text': 'View',
|
|
||||||
'title': 'Switch to tree view',
|
|
||||||
'click': function () {
|
|
||||||
switchMode('view');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// list the selected modes
|
|
||||||
var items = [];
|
|
||||||
for (var i = 0; i < modes.length; i++) {
|
|
||||||
var mode = modes[i];
|
|
||||||
var item = availableModes[mode];
|
|
||||||
if (!item) {
|
|
||||||
throw new Error('Unknown mode "' + mode + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
item.className = 'type-modes' + ((current == mode) ? ' selected' : '');
|
|
||||||
items.push(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve the title of current mode
|
|
||||||
var currentMode = availableModes[current];
|
|
||||||
if (!currentMode) {
|
|
||||||
throw new Error('Unknown mode "' + current + '"');
|
|
||||||
}
|
|
||||||
var currentTitle = currentMode.text;
|
|
||||||
|
|
||||||
// create the html element
|
|
||||||
var box = document.createElement('button');
|
|
||||||
box.className = 'modes separator';
|
|
||||||
box.innerHTML = currentTitle + ' ▾';
|
|
||||||
box.title = 'Switch editor mode';
|
|
||||||
box.onclick = function () {
|
|
||||||
var menu = new ContextMenu(items);
|
|
||||||
menu.show(box);
|
|
||||||
};
|
|
||||||
|
|
||||||
return box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
// available modes
|
||||||
create: createModeSwitcher
|
var availableModes = {
|
||||||
|
code: {
|
||||||
|
'text': 'Code',
|
||||||
|
'title': 'Switch to code highlighter',
|
||||||
|
'click': function () {
|
||||||
|
switchMode('code')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
'text': 'Form',
|
||||||
|
'title': 'Switch to form editor',
|
||||||
|
'click': function () {
|
||||||
|
switchMode('form');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
'text': 'Text',
|
||||||
|
'title': 'Switch to plain text editor',
|
||||||
|
'click': function () {
|
||||||
|
switchMode('text');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tree: {
|
||||||
|
'text': 'Tree',
|
||||||
|
'title': 'Switch to tree editor',
|
||||||
|
'click': function () {
|
||||||
|
switchMode('tree');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
'text': 'View',
|
||||||
|
'title': 'Switch to tree view',
|
||||||
|
'click': function () {
|
||||||
|
switchMode('view');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// list the selected modes
|
||||||
|
var items = [];
|
||||||
|
for (var i = 0; i < modes.length; i++) {
|
||||||
|
var mode = modes[i];
|
||||||
|
var item = availableModes[mode];
|
||||||
|
if (!item) {
|
||||||
|
throw new Error('Unknown mode "' + mode + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
item.className = 'type-modes' + ((current == mode) ? ' selected' : '');
|
||||||
|
items.push(item);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// retrieve the title of current mode
|
||||||
|
var currentMode = availableModes[current];
|
||||||
|
if (!currentMode) {
|
||||||
|
throw new Error('Unknown mode "' + current + '"');
|
||||||
|
}
|
||||||
|
var currentTitle = currentMode.text;
|
||||||
|
|
||||||
|
// create the html element
|
||||||
|
var box = document.createElement('button');
|
||||||
|
box.className = 'modes separator';
|
||||||
|
box.innerHTML = currentTitle + ' ▾';
|
||||||
|
box.title = 'Switch editor mode';
|
||||||
|
box.onclick = function () {
|
||||||
|
var menu = new ContextMenu(items);
|
||||||
|
menu.show(box);
|
||||||
|
};
|
||||||
|
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.create = createModeSwitcher;
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
|
|
||||||
// module exports
|
|
||||||
var jsoneditor = {
|
|
||||||
'JSONEditor': JSONEditor,
|
|
||||||
'util': util
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* load jsoneditor.css
|
|
||||||
*/
|
|
||||||
var loadCss = function () {
|
|
||||||
// find the script named 'jsoneditor.js' or 'jsoneditor.min.js' or
|
|
||||||
// 'jsoneditor.min.js', and use its path to find the css file to be
|
|
||||||
// loaded.
|
|
||||||
var scripts = document.getElementsByTagName('script');
|
|
||||||
for (var s = 0; s < scripts.length; s++) {
|
|
||||||
var src = scripts[s].src;
|
|
||||||
if (/(^|\/)jsoneditor([-\.]min)?.js$/.test(src)) {
|
|
||||||
var jsFile = src.split('?')[0];
|
|
||||||
var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css';
|
|
||||||
|
|
||||||
// load css file
|
|
||||||
var link = document.createElement('link');
|
|
||||||
link.type = 'text/css';
|
|
||||||
link.rel = 'stylesheet';
|
|
||||||
link.href = cssFile;
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(link);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CommonJS module exports
|
|
||||||
*/
|
|
||||||
if (typeof(module) != 'undefined' && typeof(exports) != 'undefined') {
|
|
||||||
loadCss();
|
|
||||||
module.exports = exports = jsoneditor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AMD module exports
|
|
||||||
*/
|
|
||||||
if (typeof(require) != 'undefined' && typeof(define) != 'undefined') {
|
|
||||||
loadCss();
|
|
||||||
define(function () {
|
|
||||||
return jsoneditor;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// attach the module to the window, load as a regular javascript file
|
|
||||||
window['jsoneditor'] = jsoneditor;
|
|
||||||
}
|
|
|
@ -1,335 +1,335 @@
|
||||||
define(['./modeswitcher', './util'], function (modeswitcher, util) {
|
var modeswitcher = require('./modeswitcher');
|
||||||
|
var util = require('./util');
|
||||||
|
|
||||||
// create a mixin with the functions for text mode
|
// create a mixin with the functions for text mode
|
||||||
var textmode = {};
|
var textmode = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a text editor
|
* Create a text editor
|
||||||
* @param {Element} container
|
* @param {Element} container
|
||||||
* @param {Object} [options] Object with options. available options:
|
* @param {Object} [options] Object with options. available options:
|
||||||
* {String} mode Available values:
|
* {String} mode Available values:
|
||||||
* "text" (default)
|
* "text" (default)
|
||||||
* or "code".
|
* or "code".
|
||||||
* {Number} indentation Number of indentation
|
* {Number} indentation Number of indentation
|
||||||
* spaces. 2 by default.
|
* spaces. 2 by default.
|
||||||
* {function} change Callback method
|
* {function} change Callback method
|
||||||
* triggered on change
|
* triggered on change
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
textmode.create = function (container, options) {
|
textmode.create = function (container, options) {
|
||||||
// read options
|
// read options
|
||||||
options = options || {};
|
options = options || {};
|
||||||
this.options = options;
|
this.options = options;
|
||||||
if (options.indentation) {
|
if (options.indentation) {
|
||||||
this.indentation = Number(options.indentation);
|
this.indentation = Number(options.indentation);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.indentation = 2; // number of spaces
|
this.indentation = 2; // number of spaces
|
||||||
}
|
}
|
||||||
this.mode = (options.mode == 'code') ? 'code' : 'text';
|
this.mode = (options.mode == 'code') ? 'code' : 'text';
|
||||||
if (this.mode == 'code') {
|
if (this.mode == 'code') {
|
||||||
// verify whether Ace editor is available and supported
|
// verify whether Ace editor is available and supported
|
||||||
if (typeof ace === 'undefined') {
|
if (typeof ace === 'undefined') {
|
||||||
this.mode = 'text';
|
this.mode = 'text';
|
||||||
util.log('WARNING: Cannot load code editor, Ace library not loaded. ' +
|
util.log('WARNING: Cannot load code editor, Ace library not loaded. ' +
|
||||||
'Falling back to plain text editor');
|
'Falling back to plain text editor');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var me = this;
|
var me = this;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.dom = {};
|
this.dom = {};
|
||||||
this.editor = undefined; // ace code editor
|
this.editor = undefined; // ace code editor
|
||||||
this.textarea = undefined; // plain text editor (fallback when Ace is not available)
|
this.textarea = undefined; // plain text editor (fallback when Ace is not available)
|
||||||
|
|
||||||
this.width = container.clientWidth;
|
this.width = container.clientWidth;
|
||||||
this.height = container.clientHeight;
|
this.height = container.clientHeight;
|
||||||
|
|
||||||
this.frame = document.createElement('div');
|
this.frame = document.createElement('div');
|
||||||
this.frame.className = 'jsoneditor';
|
this.frame.className = 'jsoneditor';
|
||||||
this.frame.onclick = function (event) {
|
this.frame.onclick = function (event) {
|
||||||
// prevent default submit action when the editor is located inside a form
|
// prevent default submit action when the editor is located inside a form
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
this.frame.onkeydown = function (event) {
|
this.frame.onkeydown = function (event) {
|
||||||
me._onKeyDown(event);
|
me._onKeyDown(event);
|
||||||
};
|
|
||||||
|
|
||||||
// create menu
|
|
||||||
this.menu = document.createElement('div');
|
|
||||||
this.menu.className = 'menu';
|
|
||||||
this.frame.appendChild(this.menu);
|
|
||||||
|
|
||||||
// create format button
|
|
||||||
var buttonFormat = document.createElement('button');
|
|
||||||
buttonFormat.className = 'format';
|
|
||||||
buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)';
|
|
||||||
this.menu.appendChild(buttonFormat);
|
|
||||||
buttonFormat.onclick = function () {
|
|
||||||
try {
|
|
||||||
me.format();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
me._onError(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// create compact button
|
|
||||||
var buttonCompact = document.createElement('button');
|
|
||||||
buttonCompact.className = 'compact';
|
|
||||||
buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)';
|
|
||||||
this.menu.appendChild(buttonCompact);
|
|
||||||
buttonCompact.onclick = function () {
|
|
||||||
try {
|
|
||||||
me.compact();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
me._onError(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// create mode box
|
|
||||||
if (this.options && this.options.modes && this.options.modes.length) {
|
|
||||||
var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode);
|
|
||||||
this.menu.appendChild(modeBox);
|
|
||||||
this.dom.modeBox = modeBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.content = document.createElement('div');
|
|
||||||
this.content.className = 'outer';
|
|
||||||
this.frame.appendChild(this.content);
|
|
||||||
|
|
||||||
this.container.appendChild(this.frame);
|
|
||||||
|
|
||||||
if (this.mode == 'code') {
|
|
||||||
this.editorDom = document.createElement('div');
|
|
||||||
this.editorDom.style.height = '100%'; // TODO: move to css
|
|
||||||
this.editorDom.style.width = '100%'; // TODO: move to css
|
|
||||||
this.content.appendChild(this.editorDom);
|
|
||||||
|
|
||||||
var editor = ace.edit(this.editorDom);
|
|
||||||
editor.setTheme('ace/theme/jsoneditor');
|
|
||||||
editor.setShowPrintMargin(false);
|
|
||||||
editor.setFontSize(13);
|
|
||||||
editor.getSession().setMode('ace/mode/json');
|
|
||||||
editor.getSession().setTabSize(this.indentation);
|
|
||||||
editor.getSession().setUseSoftTabs(true);
|
|
||||||
editor.getSession().setUseWrapMode(true);
|
|
||||||
this.editor = editor;
|
|
||||||
|
|
||||||
var poweredBy = document.createElement('a');
|
|
||||||
poweredBy.appendChild(document.createTextNode('powered by ace'));
|
|
||||||
poweredBy.href = 'http://ace.ajax.org';
|
|
||||||
poweredBy.target = '_blank';
|
|
||||||
poweredBy.className = '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);
|
|
||||||
|
|
||||||
if (options.change) {
|
|
||||||
// register onchange event
|
|
||||||
editor.on('change', function () {
|
|
||||||
options.change();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// load a plain text textarea
|
|
||||||
var textarea = document.createElement('textarea');
|
|
||||||
textarea.className = 'text';
|
|
||||||
textarea.spellcheck = false;
|
|
||||||
this.content.appendChild(textarea);
|
|
||||||
this.textarea = textarea;
|
|
||||||
|
|
||||||
if (options.change) {
|
|
||||||
// register onchange event
|
|
||||||
if (this.textarea.oninput === null) {
|
|
||||||
this.textarea.oninput = function () {
|
|
||||||
options.change();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// oninput is undefined. For IE8-
|
|
||||||
this.textarea.onchange = function () {
|
|
||||||
options.change();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// create menu
|
||||||
* Event handler for keydown. Handles shortcut keys
|
this.menu = document.createElement('div');
|
||||||
* @param {Event} event
|
this.menu.className = 'menu';
|
||||||
* @private
|
this.frame.appendChild(this.menu);
|
||||||
*/
|
|
||||||
textmode._onKeyDown = function (event) {
|
|
||||||
var keynum = event.which || event.keyCode;
|
|
||||||
var handled = false;
|
|
||||||
|
|
||||||
if (keynum == 220 && event.ctrlKey) {
|
|
||||||
if (event.shiftKey) { // Ctrl+Shift+\
|
|
||||||
this.compact();
|
|
||||||
}
|
|
||||||
else { // Ctrl+\
|
|
||||||
this.format();
|
|
||||||
}
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handled) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detach the editor from the DOM
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
textmode._delete = function () {
|
|
||||||
if (this.frame && this.container && this.frame.parentNode == this.container) {
|
|
||||||
this.container.removeChild(this.frame);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throw an error. If an error callback is configured in options.error, this
|
|
||||||
* callback will be invoked. Else, a regular error is thrown.
|
|
||||||
* @param {Error} err
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
textmode._onError = function(err) {
|
|
||||||
// TODO: onError is deprecated since version 2.2.0. cleanup some day
|
|
||||||
if (typeof this.onError === 'function') {
|
|
||||||
util.log('WARNING: JSONEditor.onError is deprecated. ' +
|
|
||||||
'Use options.error instead.');
|
|
||||||
this.onError(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options && typeof this.options.error === 'function') {
|
|
||||||
this.options.error(err);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compact the code in the formatter
|
|
||||||
*/
|
|
||||||
textmode.compact = function () {
|
|
||||||
var json = this.get();
|
|
||||||
var text = JSON.stringify(json);
|
|
||||||
this.setText(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format the code in the formatter
|
|
||||||
*/
|
|
||||||
textmode.format = function () {
|
|
||||||
var json = this.get();
|
|
||||||
var text = JSON.stringify(json, null, this.indentation);
|
|
||||||
this.setText(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set focus to the formatter
|
|
||||||
*/
|
|
||||||
textmode.focus = function () {
|
|
||||||
if (this.textarea) {
|
|
||||||
this.textarea.focus();
|
|
||||||
}
|
|
||||||
if (this.editor) {
|
|
||||||
this.editor.focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize the formatter
|
|
||||||
*/
|
|
||||||
textmode.resize = function () {
|
|
||||||
if (this.editor) {
|
|
||||||
var force = false;
|
|
||||||
this.editor.resize(force);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set json data in the formatter
|
|
||||||
* @param {Object} json
|
|
||||||
*/
|
|
||||||
textmode.set = function(json) {
|
|
||||||
this.setText(JSON.stringify(json, null, this.indentation));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get json data from the formatter
|
|
||||||
* @return {Object} json
|
|
||||||
*/
|
|
||||||
textmode.get = function() {
|
|
||||||
var text = this.getText();
|
|
||||||
var json;
|
|
||||||
|
|
||||||
|
// create format button
|
||||||
|
var buttonFormat = document.createElement('button');
|
||||||
|
buttonFormat.className = 'format';
|
||||||
|
buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)';
|
||||||
|
this.menu.appendChild(buttonFormat);
|
||||||
|
buttonFormat.onclick = function () {
|
||||||
try {
|
try {
|
||||||
json = util.parse(text); // this can throw an error
|
me.format();
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
// try to sanitize json, replace JavaScript notation with JSON notation
|
me._onError(err);
|
||||||
text = util.sanitize(text);
|
|
||||||
this.setText(text);
|
|
||||||
|
|
||||||
// try to parse again
|
|
||||||
json = util.parse(text); // this can throw an error
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the text contents of the editor
|
|
||||||
* @return {String} jsonText
|
|
||||||
*/
|
|
||||||
textmode.getText = function() {
|
|
||||||
if (this.textarea) {
|
|
||||||
return this.textarea.value;
|
|
||||||
}
|
|
||||||
if (this.editor) {
|
|
||||||
return this.editor.getValue();
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the text contents of the editor
|
|
||||||
* @param {String} jsonText
|
|
||||||
*/
|
|
||||||
textmode.setText = function(jsonText) {
|
|
||||||
if (this.textarea) {
|
|
||||||
this.textarea.value = jsonText;
|
|
||||||
}
|
|
||||||
if (this.editor) {
|
|
||||||
this.editor.setValue(jsonText, -1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// define modes
|
// create compact button
|
||||||
return [
|
var buttonCompact = document.createElement('button');
|
||||||
{
|
buttonCompact.className = 'compact';
|
||||||
mode: 'text',
|
buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)';
|
||||||
mixin: textmode,
|
this.menu.appendChild(buttonCompact);
|
||||||
data: 'text',
|
buttonCompact.onclick = function () {
|
||||||
load: textmode.format
|
try {
|
||||||
},
|
me.compact();
|
||||||
{
|
|
||||||
mode: 'code',
|
|
||||||
mixin: textmode,
|
|
||||||
data: 'text',
|
|
||||||
load: textmode.format
|
|
||||||
}
|
}
|
||||||
];
|
catch (err) {
|
||||||
});
|
me._onError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create mode box
|
||||||
|
if (this.options && this.options.modes && this.options.modes.length) {
|
||||||
|
var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode);
|
||||||
|
this.menu.appendChild(modeBox);
|
||||||
|
this.dom.modeBox = modeBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.content = document.createElement('div');
|
||||||
|
this.content.className = 'outer';
|
||||||
|
this.frame.appendChild(this.content);
|
||||||
|
|
||||||
|
this.container.appendChild(this.frame);
|
||||||
|
|
||||||
|
if (this.mode == 'code') {
|
||||||
|
this.editorDom = document.createElement('div');
|
||||||
|
this.editorDom.style.height = '100%'; // TODO: move to css
|
||||||
|
this.editorDom.style.width = '100%'; // TODO: move to css
|
||||||
|
this.content.appendChild(this.editorDom);
|
||||||
|
|
||||||
|
var editor = ace.edit(this.editorDom);
|
||||||
|
editor.setTheme('ace/theme/jsoneditor');
|
||||||
|
editor.setShowPrintMargin(false);
|
||||||
|
editor.setFontSize(13);
|
||||||
|
editor.getSession().setMode('ace/mode/json');
|
||||||
|
editor.getSession().setTabSize(this.indentation);
|
||||||
|
editor.getSession().setUseSoftTabs(true);
|
||||||
|
editor.getSession().setUseWrapMode(true);
|
||||||
|
this.editor = editor;
|
||||||
|
|
||||||
|
var poweredBy = document.createElement('a');
|
||||||
|
poweredBy.appendChild(document.createTextNode('powered by ace'));
|
||||||
|
poweredBy.href = 'http://ace.ajax.org';
|
||||||
|
poweredBy.target = '_blank';
|
||||||
|
poweredBy.className = '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);
|
||||||
|
|
||||||
|
if (options.change) {
|
||||||
|
// register onchange event
|
||||||
|
editor.on('change', function () {
|
||||||
|
options.change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// load a plain text textarea
|
||||||
|
var textarea = document.createElement('textarea');
|
||||||
|
textarea.className = 'text';
|
||||||
|
textarea.spellcheck = false;
|
||||||
|
this.content.appendChild(textarea);
|
||||||
|
this.textarea = textarea;
|
||||||
|
|
||||||
|
if (options.change) {
|
||||||
|
// register onchange event
|
||||||
|
if (this.textarea.oninput === null) {
|
||||||
|
this.textarea.oninput = function () {
|
||||||
|
options.change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// oninput is undefined. For IE8-
|
||||||
|
this.textarea.onchange = function () {
|
||||||
|
options.change();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for keydown. Handles shortcut keys
|
||||||
|
* @param {Event} event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
textmode._onKeyDown = function (event) {
|
||||||
|
var keynum = event.which || event.keyCode;
|
||||||
|
var handled = false;
|
||||||
|
|
||||||
|
if (keynum == 220 && event.ctrlKey) {
|
||||||
|
if (event.shiftKey) { // Ctrl+Shift+\
|
||||||
|
this.compact();
|
||||||
|
}
|
||||||
|
else { // Ctrl+\
|
||||||
|
this.format();
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detach the editor from the DOM
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
textmode._delete = function () {
|
||||||
|
if (this.frame && this.container && this.frame.parentNode == this.container) {
|
||||||
|
this.container.removeChild(this.frame);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an error. If an error callback is configured in options.error, this
|
||||||
|
* callback will be invoked. Else, a regular error is thrown.
|
||||||
|
* @param {Error} err
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
textmode._onError = function(err) {
|
||||||
|
// TODO: onError is deprecated since version 2.2.0. cleanup some day
|
||||||
|
if (typeof this.onError === 'function') {
|
||||||
|
util.log('WARNING: JSONEditor.onError is deprecated. ' +
|
||||||
|
'Use options.error instead.');
|
||||||
|
this.onError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options && typeof this.options.error === 'function') {
|
||||||
|
this.options.error(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compact the code in the formatter
|
||||||
|
*/
|
||||||
|
textmode.compact = function () {
|
||||||
|
var json = this.get();
|
||||||
|
var text = JSON.stringify(json);
|
||||||
|
this.setText(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the code in the formatter
|
||||||
|
*/
|
||||||
|
textmode.format = function () {
|
||||||
|
var json = this.get();
|
||||||
|
var text = JSON.stringify(json, null, this.indentation);
|
||||||
|
this.setText(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set focus to the formatter
|
||||||
|
*/
|
||||||
|
textmode.focus = function () {
|
||||||
|
if (this.textarea) {
|
||||||
|
this.textarea.focus();
|
||||||
|
}
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize the formatter
|
||||||
|
*/
|
||||||
|
textmode.resize = function () {
|
||||||
|
if (this.editor) {
|
||||||
|
var force = false;
|
||||||
|
this.editor.resize(force);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set json data in the formatter
|
||||||
|
* @param {Object} json
|
||||||
|
*/
|
||||||
|
textmode.set = function(json) {
|
||||||
|
this.setText(JSON.stringify(json, null, this.indentation));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get json data from the formatter
|
||||||
|
* @return {Object} json
|
||||||
|
*/
|
||||||
|
textmode.get = function() {
|
||||||
|
var text = this.getText();
|
||||||
|
var json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = util.parse(text); // this can throw an error
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// try to sanitize json, replace JavaScript notation with JSON notation
|
||||||
|
text = util.sanitize(text);
|
||||||
|
this.setText(text);
|
||||||
|
|
||||||
|
// try to parse again
|
||||||
|
json = util.parse(text); // this can throw an error
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text contents of the editor
|
||||||
|
* @return {String} jsonText
|
||||||
|
*/
|
||||||
|
textmode.getText = function() {
|
||||||
|
if (this.textarea) {
|
||||||
|
return this.textarea.value;
|
||||||
|
}
|
||||||
|
if (this.editor) {
|
||||||
|
return this.editor.getValue();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text contents of the editor
|
||||||
|
* @param {String} jsonText
|
||||||
|
*/
|
||||||
|
textmode.setText = function(jsonText) {
|
||||||
|
if (this.textarea) {
|
||||||
|
this.textarea.value = jsonText;
|
||||||
|
}
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.setValue(jsonText, -1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// define modes
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
mode: 'text',
|
||||||
|
mixin: textmode,
|
||||||
|
data: 'text',
|
||||||
|
load: textmode.format
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mode: 'code',
|
||||||
|
mixin: textmode,
|
||||||
|
data: 'text',
|
||||||
|
load: textmode.format
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
1351
src/js/treemode.js
1351
src/js/treemode.js
File diff suppressed because it is too large
Load Diff
986
src/js/util.js
986
src/js/util.js
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue