Add visual json editor

This commit is contained in:
Nick O'Leary 2019-06-11 14:44:44 +01:00
parent c97786e12c
commit e3e0378857
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
6 changed files with 923 additions and 90 deletions

View File

@ -739,7 +739,16 @@
},
"jsonEditor": {
"title": "JSON editor",
"format": "format JSON"
"format": "format JSON",
"rawMode": "Edit JSON",
"uiMode": "Visual editor",
"insertAbove": "Insert above",
"insertBelow": "Insert below",
"addItem": "Add item",
"copyPath": "Copy path to item",
"expandItems": "Expand items",
"collapseItems": "Collapse items",
"duplicate": "Duplicate"
},
"markdownEditor": {
"title": "Markdown editor",

View File

@ -185,6 +185,7 @@
}
},
_destroy: function() {
this.topContainer.remove();
},
_refreshFilter: function() {
var that = this;
@ -230,7 +231,7 @@
this.uiHeight = desiredHeight;
this._resize();
},
addItem: function(data) {
insertItemAt: function(data,index) {
var that = this;
data = data || {};
var li = $('<li>');
@ -248,7 +249,13 @@
});
}
if (!added) {
li.appendTo(this.element);
if (index <= 0) {
li.prependTo(this.element);
} else if (index > that.element.children().length-1) {
li.appendTo(this.element);
} else {
li.insertBefore(this.element.children().eq(index));
}
}
var row = $('<div/>').addClass("red-ui-editableList-item-content").appendTo(li);
row.data('data',data);
@ -293,6 +300,9 @@
},0);
}
},
addItem: function(data) {
this.insertItemAt(data,this.element.children().length)
},
addItems: function(items) {
for (var i=0; i<items.length;i++) {
this.addItem(items[i]);

View File

@ -20,6 +20,10 @@
* - data : array - initial items to display in tree
* - multi : boolean - if true, .selected will return an array of results
* otherwise, returns the first selected item
* - sortable: boolean/string - TODO: see editableList
* - rootSortable: boolean - if 'sortable' is set, then setting this to
* false, prevents items being sorted to the
* top level of the tree
*
* methods:
* - data(items) - clears existing items and replaces with new data
@ -27,25 +31,57 @@
* events:
* - treelistselect : function(event, item) {}
* - treelistconfirm : function(event,item) {}
* - treelistchangeparent: function(event,item, oldParent, newParent) {}
*
* data:
* [
* {
* label: 'Local', // label for the item
* sublabel: 'Local', // a sub-label for the item
* icon: 'fa fa-rocket', // (optional) icon for the item
* selected: true/false, // (optional) if present, display checkbox accordingly
* children: [] | function(done,item) // (optional) an array of child items, or a function
* // that will call the `done` callback with an array
* // of child items
* expanded: true/false, // show the child items by default
* deferBuild: true/false, // don't build any ui elements for the item's children
* until it is expanded by the user.
* element: // custom dom element to use for the item - ignored if `label` is set
* }
* ]
*
*
*
* var treeList = $("<div>").css({width: "100%", height: "100%"}).treeList({data:[...]})
* treeList.on('treelistselect', function(e,item) { console.log(item)})
* treeList.treeList('data',[ ... ] )
*
*
* After `data` has been added to the tree, each item is augmented the following
* properties and functions:
*
* item.parent - set to the parent item
* item.treeList.container
* item.treeList.label - the label element for the item
* item.treeList.depth - the depth in the tree (0 == root)
* item.treeList.parentList - the editableList instance this item is in
* item.treeList.remove() - removes the item from the tree
* item.treeList.makeLeaf(detachChildElements) - turns an element with children into a leaf node,
* removing the UI decoration etc.
* detachChildElements - any children with custom
* elements will be detached rather than removed
* so jQuery event handlers are preserved in case
* the child elements need to be reattached later
* item.treeList.makeParent(children) - turns an element into a parent node, adding the necessary
* UI decoration.
* item.treeList.insertChildAt(newItem,position,select) - adds a child item an the specified position.
* Optionally selects the item after adding.
* item.treeList.addChild(newItem,select) - appends a child item.
* Optionally selects the item after adding.
* item.treeList.expand(done) - expands the parent item to show children. Optional 'done' callback.
* item.treeList.collapse() - collapse the parent item to hide children.
*
*
*
*
*/
$.widget( "nodered.treeList", {
@ -116,20 +152,30 @@
});
this._data = [];
this._topList = $('<ol>').css({
this._topList = $('<ol class="red-ui-treeList-list">').css({
position:'absolute',
top: 0,
left:0,
right:0,
bottom:0
}).appendTo(wrapper).editableList({
}).appendTo(wrapper);
var topListOptions = {
addButton: false,
scrollOnAdd: false,
height: '100%',
addItem: function(container,i,item) {
that._addSubtree(that._topList,container,item,0);
}
})
};
if (this.options.rootSortable !== false && !!this.options.sortable) {
topListOptions.sortable = this.options.sortable;
topListOptions.connectWith = '.red-ui-treeList-sortable';
this._topList.addClass('red-ui-treeList-sortable');
}
this._topList.editableList(topListOptions)
if (this.options.data) {
this.data(this.options.data);
}
@ -171,23 +217,82 @@
},
_addChildren: function(container,parent,children,depth) {
var that = this;
var subtree = $('<ol>').appendTo(container).editableList({
var subtree = $('<ol class="red-ui-treeList-list">').appendTo(container).editableList({
connectWith: ".red-ui-treeList-sortable",
sortable: that.options.sortable,
addButton: false,
scrollOnAdd: false,
height: 'auto',
addItem: function(container,i,item) {
that._addSubtree(subtree,container,item,depth+1);
},
sortItems: function(data) {
var children = [];
var reparented = [];
data.each(function() {
var child = $(this).data('data');
children.push(child);
var evt = that._fixDepths(parent,child);
if (evt) {
reparented.push(evt);
}
})
if (Array.isArray(parent.children)) {
parent.children = children;
}
reparented.forEach(function(evt) {
that._trigger("changeparent",null,evt);
});
that._trigger("sort",null,parent);
}
});
if (!!that.options.sortable) {
subtree.addClass('red-ui-treeList-sortable');
}
for (var i=0;i<children.length;i++) {
children[i].parent = parent;
subtree.editableList('addItem',children[i])
}
return subtree;
},
_fixDepths: function(parent,child) {
// If child has just been moved into parent in the UI
// this will fix up the internal data structures to match.
// The calling function must take care of getting child
// into the parent.children array. The rest is up to us.
var that = this;
var reparentedEvent = null;
if (child.parent !== parent) {
reparented = true;
var oldParent = child.parent;
child.parent = parent;
reparentedEvent = {
item: child,
old: oldParent,
}
}
if (child.depth !== parent.depth+1) {
child.depth = parent.depth+1;
var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20));
child.treeList.labelPadding.width(labelPaddingWidth+'px');
if (child.element) {
$(child.element).css({
width: "calc(100% - "+(labelPaddingWidth+20+(child.icon?20:0))+"px)"
})
}
// This corrects all child item depths
if (child.children && Array.isArray(child.children)) {
child.children.forEach(function(item) {
that._fixDepths(child,item);
})
}
}
return reparentedEvent;
},
_addSubtree: function(parentList, container, item, depth) {
var that = this;
item.treeList = {};
item.treeList.depth = depth;
item.treeList.container = container;
item.treeList.parentList = parentList;
@ -196,87 +301,80 @@
if (item.parent) {
var index = item.parent.children.indexOf(item);
item.parent.children.splice(index,1)
that._trigger("sort",null,item.parent);
}
}
var labelNodeType = "<div>";
// if (item.children && item.hasOwnProperty('selected')) {
// labelNodeType = "<div>";
// }
var label = $(labelNodeType,{class:"red-ui-treeList-label"}).appendTo(container);
var label = $("<div>",{class:"red-ui-treeList-label"}).appendTo(container);
item.treeList.label = label;
if (item.class) {
label.addClass(item.class);
}
label.css({
paddingLeft: (depth*15)+'px'
})
if (item.gutter) {
item.gutter.css({
position: 'absolute'
}).appendTo(label)
}
var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
item.treeList.labelPadding = $('<span>').css({
display: "inline-block",
width: labelPaddingWidth+'px'
}).appendTo(label);
label.on('mouseover',function(e) { that._trigger('itemmouseover',e,item); })
label.on('mouseout',function(e) { that._trigger('itemmouseout',e,item); })
label.on('mouseenter',function(e) { that._trigger('itemmouseenter',e,item); })
label.on('mouseleave',function(e) { that._trigger('itemmouseleave',e,item); })
if (item.children) {
item.treeList.addChild = function(newItem,select) {
item.treeList.childList.editableList('addItem',newItem)
newItem.parent = item;
item.children.push(newItem);
if (select) {
setTimeout(function() {
that.select(newItem)
},100);
}
item.treeList.makeLeaf = function(detachChildElements) {
if (!treeListIcon.children().length) {
// Already a leaf
return
}
item.treeList.expand = function(done) {
if (container.hasClass("expanded")) {
done && done();
return;
}
if (!container.hasClass("built") && typeof item.children === 'function') {
container.addClass('built');
var childrenAdded = false;
var spinner;
var startTime = 0;
item.children(function(children) {
childrenAdded = true;
item.treeList.childList = that._addChildren(container,item,children,depth).hide();
var delta = Date.now() - startTime;
if (delta < 400) {
setTimeout(function() {
item.treeList.childList.slideDown('fast');
if (spinner) {
spinner.remove();
}
},400-delta);
} else {
item.treeList.childList.slideDown('fast');
if (spinner) {
spinner.remove();
if (detachChildElements && item.children) {
var detachChildren = function(item) {
if (item.children) {
item.children.forEach(function(child) {
if (child.element) {
child.element.detach();
}
}
done && done();
that._trigger("childrenloaded",null,item)
},item);
if (!childrenAdded) {
startTime = Date.now();
spinner = $('<div class="red-ui-treeList-spinner">').css({
"background-position": (35+depth*15)+'px 50%'
}).appendTo(container);
if (child.gutter) {
child.gutter.detach();
}
detachChildren(child);
});
}
} else {
item.treeList.childList.slideDown('fast');
done && done();
}
container.addClass("expanded");
detachChildren(item);
}
item.treeList.collapse = function() {
item.treeList.childList.slideUp('fast');
container.removeClass("expanded");
treeListIcon.empty();
if (!item.deferBuild) {
item.treeList.childList.remove();
delete item.treeList.childList;
}
$('<span class="red-ui-treeList-icon"><i class="fa fa-angle-right" /></span>').appendTo(label);
label.off("click.red-ui-treeList-expand");
treeListIcon.off("click.red-ui-treeList-expand");
delete item.children;
container.removeClass("expanded");
}
item.treeList.makeParent = function(children) {
if (treeListIcon.children().length) {
// Already a parent because we've got the angle-right icon
return;
}
$('<i class="fa fa-angle-right" />').appendTo(treeListIcon);
treeListIcon.on("click.red-ui-treeList-expand", function(e) {
e.stopPropagation();
e.preventDefault();
if (container.hasClass("expanded")) {
item.treeList.collapse();
} else {
item.treeList.expand();
}
});
// $('<span class="red-ui-treeList-icon"><i class="fa fa-folder-o" /></span>').appendTo(label);
label.on("click", function(e) {
label.on("click.red-ui-treeList-expand", function(e) {
if (container.hasClass("expanded")) {
if (item.hasOwnProperty('selected') || label.hasClass("selected")) {
item.treeList.collapse();
@ -285,9 +383,97 @@
item.treeList.expand();
}
})
} else {
$('<span class="red-ui-treeList-icon"></span>').appendTo(label);
if (!item.children) {
item.children = children||[];
item.treeList.childList = that._addChildren(container,item,item.children,depth).hide();
}
}
item.treeList.insertChildAt = function(newItem,position,select) {
newItem.parent = item;
item.children.splice(position,0,newItem);
if (!item.deferBuild) {
item.treeList.childList.editableList('insertItemAt',newItem,position)
if (select) {
setTimeout(function() {
that.select(newItem)
},100);
}
that._trigger("sort",null,item);
}
}
item.treeList.addChild = function(newItem,select) {
item.treeList.insertChildAt(newItem,item.children.length,select);
}
item.treeList.expand = function(done) {
if (!item.children) {
return;
}
if (container.hasClass("expanded")) {
done && done();
return;
}
if (!container.hasClass("built") && (item.deferBuild || typeof item.children === 'function')) {
container.addClass('built');
var childrenAdded = false;
var spinner;
var startTime = 0;
var completeBuild = function(children) {
childrenAdded = true;
item.treeList.childList = that._addChildren(container,item,children,depth).hide();
var delta = Date.now() - startTime;
if (delta < 400) {
setTimeout(function() {
item.treeList.childList.slideDown('fast');
if (spinner) {
spinner.remove();
}
},400-delta);
} else {
item.treeList.childList.slideDown('fast');
if (spinner) {
spinner.remove();
}
}
done && done();
that._trigger("childrenloaded",null,item)
}
if (typeof item.children === 'function') {
item.children(completeBuild,item);
} else {
delete item.deferBuild;
completeBuild(item.children);
}
if (!childrenAdded) {
startTime = Date.now();
spinner = $('<div class="red-ui-treeList-spinner">').css({
"background-position": (35+depth*20)+'px 50%'
}).appendTo(container);
}
} else {
if (that._loadingData) {
item.treeList.childList.show();
} else {
item.treeList.childList.slideDown('fast');
}
done && done();
}
container.addClass("expanded");
}
item.treeList.collapse = function() {
if (!item.children) {
return;
}
item.treeList.childList.slideUp('fast');
container.removeClass("expanded");
}
var treeListIcon = $('<span class="red-ui-treeList-icon"></span>').appendTo(label);
if (item.children) {
item.treeList.makeParent();
}
if (item.hasOwnProperty('selected')) {
var selectWrapper = $('<span class="red-ui-treeList-icon"></span>').appendTo(label);
var cb = $('<input class="red-ui-treeList-checkbox" type="checkbox">').prop('checked',item.selected).appendTo(selectWrapper);
@ -326,19 +512,22 @@
if (item.icon) {
$('<span class="red-ui-treeList-icon"><i class="'+item.icon+'" /></span>').appendTo(label);
}
if (item.label || item.sublabel) {
if (item.label) {
if (item.hasOwnProperty('label') || item.hasOwnProperty('sublabel')) {
if (item.hasOwnProperty('label')) {
$('<span class="red-ui-treeList-label-text"></span>').text(item.label).appendTo(label);
}
if (item.sublabel) {
if (item.hasOwnProperty('sublabel')) {
$('<span class="red-ui-treeList-sublabel-text"></span>').text(item.sublabel).appendTo(label);
}
} else if (item.element) {
$(item.element).appendTo(label);
$(item.element).css({
width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)"
})
}
if (item.children) {
if (Array.isArray(item.children)) {
if (Array.isArray(item.children) && !item.deferBuild) {
item.treeList.childList = that._addChildren(container,item,item.children,depth).hide();
}
if (item.expanded) {
@ -350,12 +539,17 @@
this._topList.editableList('empty');
},
data: function(items) {
var that = this;
if (items !== undefined) {
this._data = items;
this._topList.editableList('empty');
this._loadingData = true;
for (var i=0; i<items.length;i++) {
this._topList.editableList('addItem',items[i]);
}
setTimeout(function() {
delete that._loadingData;
},200);
this._trigger("select")
} else {

View File

@ -249,9 +249,15 @@
that.validate();
that.element.val(that.value());
that.element.trigger('change',that.propertyType,that.value());
});
this.input.on('keydown', function(evt) {
if (evt.keyCode >= 37 && evt.keyCode <= 40) {
evt.stopPropagation();
}
})
this.selectTrigger.on("click", function(event) {
event.preventDefault();
event.stopPropagation();
that._showTypeMenu();
});
this.selectTrigger.on('keydown',function(evt) {
@ -259,6 +265,7 @@
// Down
that._showTypeMenu();
}
evt.stopPropagation();
}).on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
})
@ -271,12 +278,14 @@
});
this.optionSelectTrigger.on("click", function(event) {
event.preventDefault();
event.stopPropagation();
that._showOptionSelectMenu();
}).on('keydown', function(evt) {
if (evt.keyCode === 40) {
// Down
that._showOptionSelectMenu();
}
evt.stopPropagation();
}).on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus');
}).on('focus', function() {
@ -357,6 +366,7 @@
op.on("click", function(event) {
event.preventDefault();
event.stopPropagation();
callback(opt.value);
that._hideMenu(menu);
});
@ -376,9 +386,11 @@
// UP
$(this).children(":focus").prev().trigger("focus");
} else if (evt.keyCode === 27) {
// ESCAPE
evt.preventDefault();
that._hideMenu(menu);
}
evt.stopPropagation();
})
@ -516,6 +528,7 @@
this.optionMenu.remove();
}
this.menu.remove();
this.uiSelect.remove();
},
types: function(types) {
var that = this;

View File

@ -16,7 +16,419 @@
(function() {
var template = '<script type="text/x-red" data-template-name="_json"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div></div></script>';
// var template = '<script type="text/x-red" data-template-name="_json"></script>';
var template = '<script type="text/x-red" data-template-name="_json">'+
'<ul id="red-ui-editor-type-json-tabs"></ul>'+
'<div id="red-ui-editor-type-json-tab-raw" class="red-ui-editor-type-json-tab-content hide">'+
'<div class="form-row" style="margin-bottom: 3px; text-align: right;">'+
'<button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button>'+
'</div>'+
'<div class="form-row node-text-editor-row">'+
'<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>'+
'</div>'+
'</div>'+
'<div id="red-ui-editor-type-json-tab-ui" class="red-ui-editor-type-json-tab-content hide">'+
'<div id="red-ui-editor-type-json-tab-ui-container"></div>'+
'</div>'+
'</script>';
var activeTab;
function insertNewItem(parent,index,copyIndex) {
var newValue = "";
if (parent.children.length > 0) {
switch (parent.children[Math.max(0,Math.min(parent.children.length-1,copyIndex))].type) {
case 'string': newValue = ""; break;
case 'number': newValue = 0; break;
case 'boolean': newValue = true; break;
case 'null': newValue = null; break;
case 'object': newValue = {}; break;
case 'array': newValue = []; break;
}
}
var newKey;
if (parent.type === 'array') {
newKey = parent.children.length;
} else {
var usedKeys = {};
parent.children.forEach(function(child) { usedKeys[child.key] = true })
var keyRoot = "item";
var keySuffix = 2;
newKey = keyRoot;
while(usedKeys[newKey]) {
newKey = keyRoot+"-"+(keySuffix++);
}
}
var newItem = handleItem(newKey,newValue,parent.depth+1,parent);
parent.treeList.insertChildAt(newItem, index, true);
parent.treeList.expand();
}
function showObjectMenu(button,item) {
var elementPos = button.offset();
var options = [];
if (item.parent) {
options.push({id:"red-ui-editor-type-json-menu-insert-above", icon:"fa fa-toggle-up", label:RED._('jsonEditor.insertAbove'),onselect:function(){
var index = item.parent.children.indexOf(item);
insertNewItem(item.parent,index,index);
}});
options.push({id:"red-ui-editor-type-json-menu-insert-below", icon:"fa fa-toggle-down", label:RED._('jsonEditor.insertBelow'),onselect:function(){
var index = item.parent.children.indexOf(item)+1;
insertNewItem(item.parent,index,index-1);
}});
}
if (item.type === 'array' || item.type === 'object') {
options.push({id:"red-ui-editor-type-json-menu-add-child", icon:"fa fa-plus", label:RED._('jsonEditor.addItem'),onselect:function(){
insertNewItem(item,item.children.length,item.children.length-1);
}});
}
if (item.parent) {
options.push({id:"red-ui-editor-type-json-menu-copy-path", icon:"fa fa-terminal", label:RED._('jsonEditor.copyPath'),onselect:function(){
var i = item;
var path = "";
var newPath;
while(i.parent) {
if (i.parent.type === "array") {
newPath = "["+i.key+"]";
} else {
if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(i.key)) {
newPath = i.key;
} else {
newPath = "[\""+i.key.replace(/"/,"\\\"")+"\"]"
}
}
path = newPath+(path.length>0 && path[0] !== "["?".":"")+path;
i = i.parent;
}
RED.clipboard.copyText(path,item.element,"clipboard.copyMessagePath");
}});
options.push({id:"red-ui-editor-type-json-menu-duplicate", icon:"fa fa-copy", label:RED._("jsonEditor.duplicate"),onselect:function(){
var newKey = item.key;
if (item.parent.type === 'array') {
newKey = parent.children.length;
} else {
var m = /^(.*?)(-(\d+))?$/.exec(newKey);
var usedKeys = {};
item.parent.children.forEach(function(child) { usedKeys[child.key] = true })
var keyRoot = m[1];
var keySuffix = 2;
if (m[3] !== undefined) {
keySuffix = parseInt(m[3]);
}
newKey = keyRoot;
while(usedKeys[newKey]) {
newKey = keyRoot+"-"+(keySuffix++);
}
}
var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent);
var index = item.parent.children.indexOf(item)+1;
item.parent.treeList.insertChildAt(newItem, index, true);
item.parent.treeList.expand();
}});
options.push({id:"red-ui-editor-type-json-menu-delete", icon:"fa fa-times", label:RED._('common.label.delete'),onselect:function(){
item.treeList.remove();
}});
}
if (item.type === 'array' || item.type === 'object') {
options.push(null)
options.push({id:"red-ui-editor-type-json-menu-expand-children",icon:"fa fa-angle-double-down", label:RED._('jsonEditor.expandItems'),onselect:function(){
item.treeList.expand();
item.children.forEach(function(child) {
child.treeList.expand();
})
}});
options.push({id:"red-ui-editor-type-json-menu-collapse-children",icon:"fa fa-angle-double-up", label:RED._('jsonEditor.collapseItems'),onselect:function(){
item.children.forEach(function(child) {
child.treeList.collapse();
})
}});
}
var menuOptionMenu = RED.menu.init({
id:"red-ui-editor-type-json-menu",
options: options
});
menuOptionMenu.css({
position: "absolute"
})
menuOptionMenu.on('mouseleave', function(){ $(this).hide() });
menuOptionMenu.on('mouseup', function() { $(this).hide() });
menuOptionMenu.appendTo("body");
var top = elementPos.top;
var height = menuOptionMenu.height();
var winHeight = $(window).height();
if (top+height > winHeight) {
top -= (top+height)-winHeight + 20;
}
menuOptionMenu.css({
top: top+"px",
left: elementPos.left+"px"
})
menuOptionMenu.show();
}
function parseObject(obj,depth,parent) {
var result = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
result.push(handleItem(prop,obj[prop],depth,parent));
}
}
return result;
}
function parseArray(obj,depth,parent) {
var result = [];
var l = obj.length;
for (var i=0;i<l;i++) {
result.push(handleItem(i,obj[i],depth,parent));
}
return result;
}
function handleItem(key,val,depth,parent) {
var item = {depth:depth, type: typeof val};
var container = $('<span class="red-ui-editor-type-json-editor-label">');
if (key != null) {
item.key = key;
var keyText;
if (typeof key === 'string') {
keyText = '"'+key+'"';
} else {
keyText = key;
}
var keyLabel = $('<span class="red-ui-debug-msg-object-key red-ui-editor-type-json-editor-label-key">').text(keyText).appendTo(container);
keyLabel.addClass('red-ui-debug-msg-type-'+(typeof key));
if (parent && parent.type === "array") {
keyLabel.addClass("red-ui-editor-type-json-editor-label-array-key")
}
keyLabel.on("click", function(evt) {
if (item.parent.type === 'array') {
return;
}
evt.preventDefault();
evt.stopPropagation();
var w = Math.max(150,keyLabel.width());
var keyInput = $('<input type="text" class="red-ui-editor-type-json-editor-key">').css({width:w+"px"}).val(""+item.key).insertAfter(keyLabel).typedInput({types:['str']});
$(document).on("mousedown.nr-ui-json-editor", function(evt) {
var typedInputElement = keyInput.next(".red-ui-typedInput-container")[0];
var target = evt.target;
while (target.nodeName !== 'BODY' && target !== typedInputElement && !$(target).hasClass("red-ui-typedInput-options")) {
target = target.parentElement;
}
if (target.nodeName === 'BODY') {
var newKey = keyInput.typedInput("value");
item.key = newKey;
var keyText;
if (typeof newKey === 'string') {
keyText = '"'+newKey+'"';
} else {
keyText = newKey;
}
keyLabel.text(keyText);
keyInput.remove();
keyLabel.show();
$(document).off("mousedown.nr-ui-json-editor");
$(document).off("keydown.nr-ui-json-editor");
}
});
$(document).on("keydown.nr-ui-json-editor",function(evt) {
if (evt.keyCode === 27) {
// Escape
keyInput.remove();
keyLabel.show();
$(document).off("mousedown.nr-ui-json-editor");
$(document).off("keydown.nr-ui-json-editor");
}
});
keyLabel.hide();
});
$('<span>').text(" : ").appendTo(container);
}
if (Array.isArray(val)) {
item.expanded = depth < 2;
item.type = "array";
item.deferBuild = depth >= 2;
item.children = parseArray(val,depth+1,item);
} else if (val !== null && item.type === "object") {
item.expanded = depth < 2;
item.children = parseObject(val,depth+1,item);
item.deferBuild = depth >= 2;
} else {
item.value = val;
if (val === null) {
item.type = 'null'
}
}
var valType;
var valValue = "";
var valClass;
switch(item.type) {
case 'string': valType = 'str'; valValue = '"'+item.value+'"'; valClass = "red-ui-debug-msg-type-string"; break;
case 'number': valType = 'num'; valValue = item.value; valClass = "red-ui-debug-msg-type-number";break;
case 'boolean': valType = 'bool'; valValue = item.value; valClass = "red-ui-debug-msg-type-other";break;
case 'null': valType = item.type; valValue = item.type; valClass = "red-ui-debug-msg-type-null";break;
case 'object':
valType = item.type;
valValue = item.type;//+"{"+item.children.length+"}";
valClass = "red-ui-debug-msg-type-meta";
break;
case 'array':
valType = item.type;
valValue = item.type+"["+item.children.length+"]";
valClass = "red-ui-debug-msg-type-meta";
break;
}
//
var orphanedChildren;
var valueLabel = $('<span class="red-ui-editor-type-json-editor-label-value">').addClass(valClass).text(valValue).appendTo(container);
valueLabel.on("click", function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (valType === 'str') {
valValue = valValue.substring(1,valValue.length-1);
} else if (valType === 'array') {
valValue = "";
} else if (valType === 'object') {
valValue = "";
}
var w = Math.max(150,valueLabel.width());
var val = $('<input type="text" class="red-ui-editor-type-json-editor-value">').css({width:w+"px"}).val(""+valValue).insertAfter(valueLabel).typedInput({
types:[
'str','num','bool',
{value:"null",label:"null",hasValue:false},
{value:"array",label:"array",hasValue:false},
{value:"object",label:"object",hasValue:false}
],
default: valType
});
$(document).on("mousedown.nr-ui-json-editor", function(evt) {
var typedInputElement = val.next(".red-ui-typedInput-container")[0];
var target = evt.target;
while (target.nodeName !== 'BODY' && target !== typedInputElement && !$(target).hasClass("red-ui-typedInput-options")) {
target = target.parentElement;
}
if (target.nodeName === 'BODY') {
valType = val.typedInput("type");
valValue = val.typedInput("value");
if (valType === 'num') {
valValue = valValue.trim();
if (isNaN(valValue)) {
valType = 'str';
} else if (valValue === "") {
valValue = 0;
}
}
item.value = valValue;
var valClass;
switch(valType) {
case 'str': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "string"; valClass = "red-ui-debug-msg-type-string"; valValue = '"'+valValue+'"'; break;
case 'num': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "number"; valClass = "red-ui-debug-msg-type-number"; break;
case 'bool': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "boolean"; valClass = "red-ui-debug-msg-type-other"; item.value = (valValue === "true"); break;
case 'null': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "null"; valClass = "red-ui-debug-msg-type-null"; item.value = valValue = "null"; break;
case 'object':
item.treeList.makeParent(orphanedChildren);
item.type = "object";
valClass = "red-ui-debug-msg-type-meta";
item.value = valValue = "object";
item.children.forEach(function(child,i) {
if (child.hasOwnProperty('_key')) {
child.key = child._key;
delete child._key;
var keyText;
var keyLabel = child.element.find(".red-ui-editor-type-json-editor-label-key");
keyLabel.removeClass("red-ui-editor-type-json-editor-label-array-key");
if (typeof child.key === 'string') {
keyText = '"'+child.key+'"';
keyLabel.addClass('red-ui-debug-msg-type-string');
keyLabel.removeClass('red-ui-debug-msg-type-number');
} else {
keyText = child.key;
keyLabel.removeClass('red-ui-debug-msg-type-string');
keyLabel.addClass('red-ui-debug-msg-type-number');
}
keyLabel.text(keyText);
}
})
break;
case 'array':
item.treeList.makeParent(orphanedChildren);
item.type = "array";
valClass = "red-ui-debug-msg-type-meta";
item.value = valValue = "array["+(item.children.length)+"]";
item.children.forEach(function(child,i) {
child._key = child.key;
child.key = i;
child.element.find(".red-ui-editor-type-json-editor-label-key")
.addClass("red-ui-editor-type-json-editor-label-array-key")
.text(""+child.key)
.removeClass('red-ui-debug-msg-type-string')
.addClass('red-ui-debug-msg-type-number');
})
break;
}
valueLabel.text(valValue).removeClass().addClass("red-ui-editor-type-json-editor-label-value "+valClass);
val.remove();
valueLabel.show();
$(document).off("mousedown.nr-ui-json-editor");
$(document).off("keydown.nr-ui-json-editor");
}
})
$(document).on("keydown.nr-ui-json-editor",function(evt) {
if (evt.keyCode === 27) {
// Escape
val.remove();
valueLabel.show();
if (valType === 'str') {
valValue = '"'+valValue+'"';
}
$(document).off("mousedown.nr-ui-json-editor");
$(document).off("keydown.nr-ui-json-editor");
}
});
valueLabel.hide();
})
item.gutter = $('<span class="red-ui-editor-type-json-editor-item-gutter"></span>');
if (parent) {//red-ui-editor-type-json-editor-item-handle
$('<span class="red-ui-editor-type-json-editor-item-handle"><i class="fa fa-bars"></span>').appendTo(item.gutter);
} else {
$('<span></span>').appendTo(item.gutter);
}
$('<button type="button" class="editor-button editor-button-small"><i class="fa fa-caret-down"></button>').appendTo(item.gutter).on("click", function(evt) {
evt.preventDefault();
evt.stopPropagation();
showObjectMenu($(this), item);
});
item.element = container;
return item;
}
function convertToObject(item) {
var element;
switch (item.type) {
case 'string': element = item.value; break;
case 'number': element = Number(item.value); break;
case 'boolean': element = item.value; break;
case 'null': element = null; break;
case 'object':
element = {};
item.children.forEach(function(child) {
element[child.key] = convertToObject(child);
})
break;
case 'array':
element = item.children.map(function(child) {
return convertToObject(child);
})
break;
}
return element;
}
var definition = {
show: function(options) {
@ -41,9 +453,11 @@
return false;
}
}
var rootNode;
var trayOptions = {
title: options.title,
width: "inherit",
width: options.width||700,
buttons: [
{
id: "node-dialog-cancel",
@ -60,25 +474,60 @@
if (options.requireValid && !checkValid()) {
return;
}
onComplete(expressionEditor.getValue());
var result;
if (activeTab === "json-ui") {
if (rootNode) {
result = JSON.stringify(convertToObject(rootNode),null,4);
} else {
result = expressionEditor.getValue();
}
} else if (activeTab === "json-raw") {
result = expressionEditor.getValue();
}
onComplete && onComplete(result);
RED.tray.close();
}
}
],
resize: function(dimensions) {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
var height = $(".red-ui-editor-type-json-tab-content").height();
$(".node-text-editor").css("height",(height-45)+"px");
expressionEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.red-ui-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"});
var filterDepth = Infinity;
var list = $('<div class="red-ui-debug-msg-payload red-ui-editor-type-json-editor">').appendTo(container).treeList({
rootSortable: false,
sortable: ".red-ui-editor-type-json-editor-item-handle",
}).on("treelistchangeparent", function(event, evt) {
if (evt.old.type === 'array') {
evt.old.element.find(".red-ui-editor-type-json-editor-label-type").text("array["+evt.old.children.length+"]");
}
if (evt.item.parent.type === 'array') {
evt.item.parent.element.find(".red-ui-editor-type-json-editor-label-type").text("array["+evt.item.parent.children.length+"]");
}
}).on("treelistsort", function(event, item) {
item.children.forEach(function(child,i) {
if (item.type === 'array') {
child.key = i;
child.element.find(".red-ui-editor-type-json-editor-label-key")
.text(child.key)
.removeClass('red-ui-debug-msg-type-string')
.addClass('red-ui-debug-msg-type-number');
} else {
child.element.find(".red-ui-editor-type-json-editor-label-key")
.text('"'+child.key+'"')
.removeClass('red-ui-debug-msg-type-number')
.addClass('red-ui-debug-msg-type-string');
}
})
});
expressionEditor = RED.editor.createEditor({
id: 'node-input-json',
value: "",
@ -103,9 +552,56 @@
expressionEditor.getSession().setValue(v||"",-1);
});
dialogForm.i18n();
var finishedBuild = false;
var tabs = RED.tabs.create({
element: $("#red-ui-editor-type-json-tabs"),
onchange:function(tab) {
activeTab = tab.id;
$(".red-ui-editor-type-json-tab-content").hide();
if (finishedBuild) {
if (tab.id === "json-raw") {
if (rootNode) {
var result = JSON.stringify(convertToObject(rootNode),null,4);
expressionEditor.getSession().setValue(result||"",-1);
}
} else if (tab.id === "json-ui") {
var raw = expressionEditor.getValue().trim() ||"{}";
try {
var parsed = JSON.parse(raw);
rootNode = handleItem(null,parsed,0,null);
rootNode.class = "red-ui-editor-type-json-root-node"
list.treeList('data',[rootNode]);
} catch(err) {
rootNode = null;
list.treeList('data',[{
label: "Invalid JSON: "+err.toString()
}]);
}
}
}
tab.content.show();
trayOptions.resize();
}
})
tabs.addTab({
id: 'json-raw',
label: RED._('jsonEditor.rawMode'),
content: $("#red-ui-editor-type-json-tab-raw")
});
tabs.addTab({
id: 'json-ui',
label: RED._('jsonEditor.uiMode'),
content: $("#red-ui-editor-type-json-tab-ui")
});
finishedBuild = true;
},
close: function() {
expressionEditor.destroy();
// expressionEditor.destroy();
if (options.onclose) {
options.onclose();
}

View File

@ -461,3 +461,114 @@ button.red-ui-button-small
margin: 2px;
}
}
.red-ui-editor input.red-ui-editor-type-json-editor-key {
width: 150px;
}
.red-ui-editor-type-json-editor {
height: calc(100% - 10px);
.red-ui-treeList-container {
background: $secondary-background;
}
.red-ui-treeList-label {
padding-top: 0;
padding-bottom: 0;
white-space: nowrap;
min-height: 35px;
.red-ui-treeList-icon:before {
content:'';
display: inline-block;
height: 35px;
vertical-align: middle;
}
> span, > span > span {
vertical-align: middle;
}
&:hover, &:hover .red-ui-treeList-sublabel-text {
background: $secondary-background-disabled;
.red-ui-editor-type-json-editor-item-gutter {
> span, > button {
display: inline-block;
}
}
}
&.selected {
.red-ui-editor-type-json-editor-item-gutter {
background: $secondary-background-hover;
}
&:hover {
.red-ui-editor-type-json-editor-item-gutter {
background: $secondary-background-selected;
}
}
}
&.red-ui-editor-type-json-root-node {
.red-ui-editor-type-json-editor-item-gutter {
> span, > button {
display: inline-block;
}
}
}
}
}
.red-ui-editor-type-json-editor-controls {
height: 34px;
line-height: 34px;
display: none;
}
.red-ui-editor-type-json-editor-key {
width: 100px;
}
.red-ui-editor-type-json-editor-label {
display: inline-block;
white-space: pre-wrap;
}
.red-ui-editor-type-json-editor-label-value {
min-width: 200px;
}
.red-ui-editor-type-json-editor-label-value,
.red-ui-editor-type-json-editor-label-key {
display: inline-block;
box-sizing: border-box;
min-height: 34px;
line-height: 30px;
padding: 0 2px;
border: 2px solid rgba(0,0,0,0);
border-radius: 3px;
&:not(.red-ui-editor-type-json-editor-label-array-key):hover {
border-color: $list-item-background-hover;
border-style: dashed;
}
}
.red-ui-editor-type-json-editor-item-gutter {
width: 48px;
padding-left: 4px;
height: 100%;
line-height: 35px;
color: $tertiary-text-color;
background: $secondary-background-disabled;
> span {
display: inline-block;
height: 35px;
line-height: 35px;
width: 20px;
text-align:center;
}
> span, > button {
display: none;
}
}
.red-ui-editor-type-json-editor-item-handle {
cursor: move;
}
.red-ui-editor-type-json-tab-content {
position: relative;
height: calc(100% - 40px);
}