Implementing "show more" for large arrays (WIP)
This commit is contained in:
parent
358ef3086c
commit
904110d141
|
@ -5,7 +5,7 @@ https://github.com/josdejong/jsoneditor
|
|||
|
||||
## not yet released, version 5.15.1
|
||||
|
||||
- Fixed index numbers of Array itesm not being updated after sorting.
|
||||
- Fixed index numbers of Array items not being updated after sorting.
|
||||
|
||||
|
||||
## 2018-05-02, version 5.15.0
|
||||
|
|
|
@ -27,11 +27,11 @@ div.jsoneditor-value {
|
|||
|
||||
div.jsoneditor-readonly {
|
||||
min-width: 16px;
|
||||
color: gray;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
div.jsoneditor-empty {
|
||||
border-color: lightgray;
|
||||
border-color: #d3d3d3;
|
||||
border-style: dashed;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ div.jsoneditor-empty {
|
|||
div.jsoneditor-field.jsoneditor-empty::after,
|
||||
div.jsoneditor-value.jsoneditor-empty::after {
|
||||
pointer-events: none;
|
||||
color: lightgray;
|
||||
color: #d3d3d3;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ a.jsoneditor-value.jsoneditor-url:focus {
|
|||
div.jsoneditor td.jsoneditor-separator {
|
||||
padding: 3px 0;
|
||||
vertical-align: top;
|
||||
color: gray;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
div.jsoneditor-field[contenteditable=true]:focus,
|
||||
|
@ -176,6 +176,23 @@ div.jsoneditor-tree button.jsoneditor-invisible {
|
|||
background: none;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree div.jsoneditor-show-more {
|
||||
display: inline-block;
|
||||
padding: 3px 4px;
|
||||
margin: 2px 0;
|
||||
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 3px;
|
||||
color: #808080;
|
||||
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
div.jsoneditor-tree div.jsoneditor-show-more a {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
div.jsoneditor {
|
||||
color: #1A1A1A;
|
||||
border: 1px solid #3883fa;
|
||||
|
|
127
src/js/Node.js
127
src/js/Node.js
|
@ -3,6 +3,7 @@
|
|||
var naturalSort = require('javascript-natural-sort');
|
||||
var ContextMenu = require('./ContextMenu');
|
||||
var appendNodeFactory = require('./appendNodeFactory');
|
||||
var showMoreNodeFactory = require('./showMoreNodeFactory');
|
||||
var util = require('./util');
|
||||
var translate = require('./i18n').translate;
|
||||
|
||||
|
@ -39,6 +40,12 @@ function Node (editor, params) {
|
|||
// debounce interval for keyboard input in milliseconds
|
||||
Node.prototype.DEBOUNCE_INTERVAL = 150;
|
||||
|
||||
Node.prototype.MAX_VISIBLE_CHILDS = 100;
|
||||
Node.prototype.MAX_VISIBLE_CHILDS = 10; // TODO: remove this line, use 100 instead of 10
|
||||
|
||||
// default value for the max visible childs of large arrays
|
||||
Node.prototype.maxVisibleChilds = Node.prototype.MAX_VISIBLE_CHILDS;
|
||||
|
||||
/**
|
||||
* Determine whether the field and/or value of this node are editable
|
||||
* @private
|
||||
|
@ -440,6 +447,7 @@ Node.prototype.clone = function() {
|
|||
clone.value = this.value;
|
||||
clone.valueInnerText = this.valueInnerText;
|
||||
clone.expanded = this.expanded;
|
||||
clone.maxVisibleChilds = this.maxVisibleChilds;
|
||||
|
||||
if (this.childs) {
|
||||
// an object or array
|
||||
|
@ -527,20 +535,45 @@ Node.prototype.showChilds = function() {
|
|||
var table = tr ? tr.parentNode : undefined;
|
||||
if (table) {
|
||||
// show row with append button
|
||||
var append = this.getAppend();
|
||||
var nextTr = tr.nextSibling;
|
||||
if (nextTr) {
|
||||
table.insertBefore(append, nextTr);
|
||||
}
|
||||
else {
|
||||
table.appendChild(append);
|
||||
var append = this.getAppendDom();
|
||||
if (!append.parentNode) {
|
||||
var nextTr = tr.nextSibling;
|
||||
if (nextTr) {
|
||||
table.insertBefore(append, nextTr);
|
||||
}
|
||||
else {
|
||||
table.appendChild(append);
|
||||
}
|
||||
}
|
||||
|
||||
// show childs
|
||||
this.childs.forEach(function (child) {
|
||||
table.insertBefore(child.getDom(), append);
|
||||
var iMax = Math.min(this.childs.length, this.maxVisibleChilds);
|
||||
var nextTr = this._getNextTr();
|
||||
for (var i = 0; i < iMax; i++) {
|
||||
var child = this.childs[i];
|
||||
if (!child.getDom().parentNode) {
|
||||
table.insertBefore(child.getDom(), nextTr);
|
||||
}
|
||||
child.showChilds();
|
||||
});
|
||||
}
|
||||
|
||||
// show "show more childs" if limited
|
||||
var showMore = this.getShowMoreDom();
|
||||
var nextTr = this._getNextTr();
|
||||
if (!showMore.parentNode) {
|
||||
table.insertBefore(showMore, nextTr);
|
||||
}
|
||||
this.showMore.updateDom(); // to update the counter
|
||||
}
|
||||
};
|
||||
|
||||
Node.prototype._getNextTr = function() {
|
||||
if (this.showMore && this.showMore.getDom().parentNode) {
|
||||
return this.showMore.getDom();
|
||||
}
|
||||
|
||||
if (this.append && this.append.getDom().parentNode) {
|
||||
return this.append.getDom();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -570,7 +603,7 @@ Node.prototype.hideChilds = function() {
|
|||
}
|
||||
|
||||
// hide append row
|
||||
var append = this.getAppend();
|
||||
var append = this.getAppendDom();
|
||||
if (append.parentNode) {
|
||||
append.parentNode.removeChild(append);
|
||||
}
|
||||
|
@ -579,6 +612,15 @@ Node.prototype.hideChilds = function() {
|
|||
this.childs.forEach(function (child) {
|
||||
child.hide();
|
||||
});
|
||||
|
||||
// hide "show more" row
|
||||
var showMore = this.getShowMoreDom();
|
||||
if (showMore.parentNode) {
|
||||
showMore.parentNode.removeChild(showMore);
|
||||
}
|
||||
|
||||
// reset max visible childs
|
||||
delete this.maxVisibleChilds;
|
||||
};
|
||||
|
||||
|
||||
|
@ -614,7 +656,7 @@ Node.prototype.appendChild = function(node) {
|
|||
if (this.expanded) {
|
||||
// insert into the DOM, before the appendRow
|
||||
var newTr = node.getDom();
|
||||
var appendTr = this.getAppend();
|
||||
var appendTr = this.getAppendDom();
|
||||
var table = appendTr ? appendTr.parentNode : undefined;
|
||||
if (appendTr && table) {
|
||||
table.insertBefore(newTr, appendTr);
|
||||
|
@ -1084,7 +1126,7 @@ Node.prototype.changeType = function (newType) {
|
|||
var table = this.dom.tr ? this.dom.tr.parentNode : undefined;
|
||||
var lastTr;
|
||||
if (this.expanded) {
|
||||
lastTr = this.getAppend();
|
||||
lastTr = this.getAppendDom();
|
||||
}
|
||||
else {
|
||||
lastTr = this.getDom();
|
||||
|
@ -1719,9 +1761,9 @@ Node.onDrag = function (nodes, event) {
|
|||
topThis += 27;
|
||||
// TODO: dangerous to suppose the height of the appendNode a constant of 27 px.
|
||||
}
|
||||
}
|
||||
|
||||
trNext = trNext.nextSibling;
|
||||
trNext = trNext.nextSibling;
|
||||
}
|
||||
}
|
||||
while (trNext && mouseY > topThis + heightNext);
|
||||
|
||||
|
@ -2033,6 +2075,30 @@ Node.prototype.updateDom = function (options) {
|
|||
this._updateDomIndexes();
|
||||
}
|
||||
|
||||
// show/hide childs exceeding the maxVisibleChilds
|
||||
if (this.childs) {
|
||||
var iMax = Math.min(this.childs.length, this.maxVisibleChilds);
|
||||
var child;
|
||||
|
||||
// append childs to DOM when not reaching maxVisibleChilds
|
||||
var i = iMax - 1;
|
||||
var nextTr = this._getNextTr();
|
||||
if (nextTr)
|
||||
while (this.childs[i] && !this.childs[i].getDom().parentNode) {
|
||||
child = this.childs[i].getDom();
|
||||
nextTr.parentNode.insertBefore(child, nextTr);
|
||||
i--;
|
||||
}
|
||||
|
||||
// remove childs from DOM when exceeding maxVisibleChilds
|
||||
var j = iMax;
|
||||
while (this.childs[j] && this.childs[j].getDom().parentNode) {
|
||||
child = this.childs[j].getDom();
|
||||
child.parentNode.removeChild(child);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (options && options.recurse === true) {
|
||||
// recurse is true or undefined. update childs recursively
|
||||
if (this.childs) {
|
||||
|
@ -2046,6 +2112,11 @@ Node.prototype.updateDom = function (options) {
|
|||
if (this.append) {
|
||||
this.append.updateDom();
|
||||
}
|
||||
|
||||
// update "show more" text at the bottom of large arrays
|
||||
if (this.showMore) {
|
||||
this.showMore.updateDom();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2577,7 +2648,7 @@ Node.prototype.onKeyDown = function (event) {
|
|||
}
|
||||
else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow left
|
||||
if (lastNode.expanded) {
|
||||
var appendDom = lastNode.getAppend();
|
||||
var appendDom = lastNode.getAppendDom();
|
||||
nextDom = appendDom ? appendDom.nextSibling : undefined;
|
||||
}
|
||||
else {
|
||||
|
@ -3004,12 +3075,7 @@ Node.prototype.sort = function (direction) {
|
|||
this.sortOrder = (order == 1) ? 'asc' : 'desc';
|
||||
|
||||
// update the index numbering
|
||||
if (this.type === 'array') {
|
||||
this.childs.forEach(function (child, index) {
|
||||
child.index = index;
|
||||
child.updateDom();
|
||||
});
|
||||
}
|
||||
this._updateDomIndexes();
|
||||
|
||||
this.editor._onAction('sort', {
|
||||
node: this,
|
||||
|
@ -3024,9 +3090,9 @@ Node.prototype.sort = function (direction) {
|
|||
|
||||
/**
|
||||
* Create a table row with an append button.
|
||||
* @return {HTMLElement | undefined} buttonAppend or undefined when inapplicable
|
||||
* @return {HTMLElement | undefined} tr with the AppendNode contents
|
||||
*/
|
||||
Node.prototype.getAppend = function () {
|
||||
Node.prototype.getAppendDom = function () {
|
||||
if (!this.append) {
|
||||
this.append = new AppendNode(this.editor);
|
||||
this.append.setParent(this);
|
||||
|
@ -3034,6 +3100,17 @@ Node.prototype.getAppend = function () {
|
|||
return this.append.getDom();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a table row with an showMore button and text
|
||||
* @return {HTMLElement | undefined} tr with the AppendNode contents
|
||||
*/
|
||||
Node.prototype.getShowMoreDom = function () {
|
||||
if (!this.showMore) {
|
||||
this.showMore = new ShowMoreNode(this.editor, this);
|
||||
}
|
||||
return this.showMore.getDom();
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the node from an event target
|
||||
* @param {Node} target
|
||||
|
@ -3648,6 +3725,8 @@ Node.prototype._escapeJSON = function (text) {
|
|||
};
|
||||
|
||||
// TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode
|
||||
// idea: introduce properties .isAppendNode and .isNode and use that instead of instanceof AppendNode checks
|
||||
var AppendNode = appendNodeFactory(Node);
|
||||
var ShowMoreNode = showMoreNodeFactory(Node);
|
||||
|
||||
module.exports = Node;
|
||||
|
|
|
@ -77,7 +77,7 @@ function appendNodeFactory(Node) {
|
|||
/**
|
||||
* Update the HTML dom of the Node
|
||||
*/
|
||||
AppendNode.prototype.updateDom = function () {
|
||||
AppendNode.prototype.updateDom = function(options) {
|
||||
var dom = this.dom;
|
||||
var tdAppend = dom.td;
|
||||
if (tdAppend) {
|
||||
|
@ -189,7 +189,7 @@ function appendNodeFactory(Node) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Handle an event. The event is catched centrally by the editor
|
||||
* Handle an event. The event is caught centrally by the editor
|
||||
* @param {Event} event
|
||||
*/
|
||||
AppendNode.prototype.onEvent = function (event) {
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
'use strict';
|
||||
|
||||
var translate = require('./i18n').translate;
|
||||
|
||||
/**
|
||||
* A factory function to create an ShowMoreNode, which depends on a Node
|
||||
* @param {function} Node
|
||||
*/
|
||||
function showMoreNodeFactory(Node) {
|
||||
/**
|
||||
* @constructor ShowMoreNode
|
||||
* @extends Node
|
||||
* @param {TreeEditor} editor
|
||||
* @param {Node} parent
|
||||
* Create a new ShowMoreNode. This is a special node which is created
|
||||
* for arrays or objects having more than 100 items
|
||||
*/
|
||||
function ShowMoreNode (editor, parent) {
|
||||
/** @type {TreeEditor} */
|
||||
this.editor = editor;
|
||||
this.parent = parent;
|
||||
this.dom = {};
|
||||
}
|
||||
|
||||
ShowMoreNode.prototype = new Node();
|
||||
|
||||
/**
|
||||
* Return a table row with an append button.
|
||||
* @return {Element} dom TR element
|
||||
*/
|
||||
ShowMoreNode.prototype.getDom = function () {
|
||||
if (this.dom.tr) {
|
||||
return this.dom.tr;
|
||||
}
|
||||
|
||||
this._updateEditability();
|
||||
|
||||
// display "show more"
|
||||
if (!this.dom.tr) {
|
||||
var me = this;
|
||||
var parent = this.parent;
|
||||
var showMoreButton = document.createElement('a');
|
||||
showMoreButton.appendChild(document.createTextNode('show\u00A0more'));
|
||||
showMoreButton.href = '#';
|
||||
showMoreButton.onclick = function (event) {
|
||||
// TODO: use callback instead of accessing a method of the parent
|
||||
parent.maxVisibleChilds += Node.prototype.MAX_VISIBLE_CHILDS;
|
||||
me.updateDom();
|
||||
parent.showChilds();
|
||||
|
||||
event.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
var showAllButton = document.createElement('a');
|
||||
showAllButton.appendChild(document.createTextNode('show\u00A0all'));
|
||||
showAllButton.href = '#';
|
||||
showAllButton.onclick = function (event) {
|
||||
// TODO: use callback instead of accessing a method of the parent
|
||||
parent.maxVisibleChilds = Infinity;
|
||||
me.updateDom();
|
||||
parent.showChilds();
|
||||
|
||||
event.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
var moreContents = document.createElement('div');
|
||||
var moreText = document.createTextNode(this._getShowMoreText());
|
||||
moreContents.className = 'jsoneditor-show-more';
|
||||
moreContents.appendChild(moreText);
|
||||
moreContents.appendChild(showMoreButton);
|
||||
moreContents.appendChild(document.createTextNode('. '));
|
||||
moreContents.appendChild(showAllButton);
|
||||
moreContents.appendChild(document.createTextNode('. '));
|
||||
|
||||
var tdContents = document.createElement('td');
|
||||
tdContents.appendChild(moreContents);
|
||||
|
||||
var moreTr = document.createElement('tr');
|
||||
moreTr.appendChild(document.createElement('td'));
|
||||
moreTr.appendChild(document.createElement('td'));
|
||||
moreTr.appendChild(tdContents);
|
||||
this.dom.tr = moreTr;
|
||||
this.dom.moreContents = moreContents;
|
||||
this.dom.moreText = moreText;
|
||||
}
|
||||
|
||||
this.updateDom();
|
||||
|
||||
return this.dom.tr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the HTML dom of the Node
|
||||
*/
|
||||
ShowMoreNode.prototype.updateDom = function(options) {
|
||||
if (this.isVisible()) {
|
||||
if (!this.dom.tr.parentNode) {
|
||||
var nextTr = this.parent._getNextTr();
|
||||
nextTr.parentNode.insertBefore(this.dom.tr, nextTr);
|
||||
}
|
||||
// this.dom.tr.style.display = '';
|
||||
|
||||
// update the counts in the text
|
||||
this.dom.moreText.nodeValue = this._getShowMoreText();
|
||||
|
||||
// update left margin
|
||||
this.dom.moreContents.style.marginLeft = (this.getLevel() + 2) * 24 + 'px';
|
||||
}
|
||||
else {
|
||||
if (this.dom.tr && this.dom.tr.parentNode) {
|
||||
this.dom.tr.parentNode.removeChild(this.dom.tr);
|
||||
}
|
||||
|
||||
// this.dom.tr.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
ShowMoreNode.prototype._getShowMoreText = function() {
|
||||
// TODO: implement in translate
|
||||
var childs = this.type === 'array' ? 'items' : 'properties';
|
||||
return 'displaying ' + this.parent.maxVisibleChilds +
|
||||
' of ' + this.parent.childs.length + ' ' + childs + '. ';
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the ShowMoreNode is currently visible.
|
||||
* the ShowMoreNode is visible when it's parent has more childs than
|
||||
* the current maxVisibleChilds
|
||||
* @return {boolean} isVisible
|
||||
*/
|
||||
ShowMoreNode.prototype.isVisible = function () {
|
||||
return this.parent.expanded && this.parent.childs.length > this.parent.maxVisibleChilds;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle an event. The event is caught centrally by the editor
|
||||
* @param {Event} event
|
||||
*/
|
||||
ShowMoreNode.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, 'jsoneditor-selected');
|
||||
// this.showContextMenu(dom.menu, function () {
|
||||
// util.removeClassName(dom.menu, 'jsoneditor-selected');
|
||||
// highlighter.unlock();
|
||||
// highlighter.unhighlight();
|
||||
// });
|
||||
// }
|
||||
|
||||
if (type === 'keydown') {
|
||||
this.onKeyDown(event);
|
||||
}
|
||||
};
|
||||
|
||||
return ShowMoreNode;
|
||||
}
|
||||
|
||||
module.exports = showMoreNodeFactory;
|
|
@ -0,0 +1,72 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||
|
||||
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
|
||||
<script src="../dist/jsoneditor.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
font: 10.5pt arial;
|
||||
color: #4d4d4d;
|
||||
line-height: 150%;
|
||||
width: 500px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
#jsoneditor {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>
|
||||
Switch editor mode using the mode box.
|
||||
Note that the mode can be changed programmatically as well using the method
|
||||
<code>editor.setMode(mode)</code>, try it in the console of your browser.
|
||||
</p>
|
||||
|
||||
<form>
|
||||
<div id="jsoneditor"></div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
var container = document.getElementById('jsoneditor');
|
||||
|
||||
var options = {
|
||||
mode: 'tree',
|
||||
modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
|
||||
onError: function (err) {
|
||||
console.error(err);
|
||||
alert(err.toString());
|
||||
},
|
||||
onChange: function () {
|
||||
console.log('change');
|
||||
},
|
||||
indentation: 4,
|
||||
escapeUnicode: true
|
||||
};
|
||||
|
||||
var json = {
|
||||
array: [],
|
||||
object: { a: 2, b: 3}
|
||||
};
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
json.array.push({
|
||||
name: 'Item ' + i,
|
||||
index: i,
|
||||
time: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
var editor = new JSONEditor(container, options, json);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue