diff --git a/package-lock.json b/package-lock.json
index bdc504d..be40c61 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2789,6 +2789,11 @@
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k="
},
+ "jmespath": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
+ "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
+ },
"json-loader": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.4.tgz",
diff --git a/package.json b/package.json
index a93963a..d2961d9 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"ajv": "5.5.2",
"brace": "0.11.0",
"javascript-natural-sort": "0.7.1",
+ "jmespath": "0.15.0",
"picomodal": "3.0.0"
},
"devDependencies": {
diff --git a/src/css/contextmenu.css b/src/css/contextmenu.css
index 2a80f2f..d8287ee 100644
--- a/src/css/contextmenu.css
+++ b/src/css/contextmenu.css
@@ -177,6 +177,14 @@ div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-ic
background-position: -192px 0;
}
+div.jsoneditor-contextmenu button.jsoneditor-transform > div.jsoneditor-icon {
+ background-position: -216px -24px;
+}
+div.jsoneditor-contextmenu button.jsoneditor-transform:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-transform:focus > div.jsoneditor-icon {
+ background-position: -216px 0;
+}
+
/* ContextMenu - sub menu */
div.jsoneditor-contextmenu ul li button.jsoneditor-selected,
@@ -278,6 +286,9 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
border-radius: 2px !important;
padding: 45px 15px 15px 15px !important;
box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3) !important;
+
+ color: #4d4d4d;
+ line-height: 1.3em;
}
.jsoneditor-modal .pico-modal-header {
@@ -305,7 +316,6 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
vertical-align: middle;
font-size: 10pt;
font-family: arial, sans-serif;
- color: #4d4d4d;
}
.jsoneditor-modal table td:first-child {
@@ -338,6 +348,15 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
cursor: pointer;
}
+.jsoneditor-modal input {
+ padding: 4px 20px;
+}
+
+.jsoneditor-modal input[type="text"] {
+ padding: 4px;
+ cursor: inherit;
+}
+
.jsoneditor-modal .jsoneditor-select-wrapper {
position: relative;
}
@@ -398,7 +417,3 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
border-color: #3883fa;
color: white;
}
-
-.jsoneditor-modal input {
- padding: 4px 20px;
-}
diff --git a/src/css/img/jsoneditor-icons.svg b/src/css/img/jsoneditor-icons.svg
index 9e48bdc..ad25fbc 100644
--- a/src/css/img/jsoneditor-icons.svg
+++ b/src/css/img/jsoneditor-icons.svg
@@ -7,7 +7,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="216"
+ width="240"
height="144"
id="svg4136"
version="1.1"
@@ -30,7 +30,7 @@
+
+
+
+
+
+ id="g4299"
+ style="stroke:none">
+ x="7.0000048"
+ y="10.999998"
+ width="9.9999924"
+ height="1.9999986"
+ id="svg_1-1"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
+ x="11.000005"
+ y="7.0000114"
+ width="1.9999955"
+ height="9.9999838"
+ id="svg_1-1-1"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
+
+
+ x="7.0000048"
+ y="10.999998"
+ width="9.9999924"
+ height="1.9999986"
+ id="svg_1-1-0"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
-
-
-
-
-
-
-
-
-
+ x="11.000005"
+ y="7.0000114"
+ width="1.9999955"
+ height="9.9999838"
+ id="svg_1-1-1-9"
+ style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
+
+
+
+
+
+ x="198"
+ y="10.999999"
+ width="7.9999909"
+ height="1.9999965"
+ id="svg_1-7-5-3" />
+ id="rect4374"
+ height="1.9999946"
+ width="11.999995"
+ y="7.0000005"
+ x="198"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ id="rect4376"
+ height="1.9999995"
+ width="3.9999928"
+ y="14.999996"
+ x="198"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/js/History.js b/src/js/History.js
index a363700..3a5f275 100644
--- a/src/js/History.js
+++ b/src/js/History.js
@@ -120,19 +120,19 @@ function History (editor) {
}
},
- 'sort': {
+ 'transform': {
'undo': function (params) {
var node = params.node;
node.hideChilds();
node.childs = params.oldChilds;
- node._updateDomIndexes();
+ node.updateDom({updateIndexes: true});
node.showChilds();
},
'redo': function (params) {
var node = params.node;
node.hideChilds();
node.childs = params.newChilds;
- node._updateDomIndexes();
+ node.updateDom({updateIndexes: true});
node.showChilds();
}
}
diff --git a/src/js/Node.js b/src/js/Node.js
index ca53f56..c17921e 100644
--- a/src/js/Node.js
+++ b/src/js/Node.js
@@ -1,10 +1,12 @@
'use strict';
+var jmespath = require('jmespath');
var naturalSort = require('javascript-natural-sort');
var ContextMenu = require('./ContextMenu');
var appendNodeFactory = require('./appendNodeFactory');
var showMoreNodeFactory = require('./showMoreNodeFactory');
var showSortModal = require('./showSortModal');
+var showTransformModal = require('./showTransformModal');
var util = require('./util');
var translate = require('./i18n').translate;
@@ -326,17 +328,16 @@ Node.prototype.getField = function() {
*/
Node.prototype.setValue = function(value, type) {
var childValue, child, visible;
+ var notUpdateDom = false;
// first clear all current childs (if any)
var childs = this.childs;
if (childs) {
while (childs.length) {
- this.removeChild(childs[0]);
+ this.removeChild(childs[0], notUpdateDom);
}
}
- // TODO: remove the DOM of this Node
-
this.type = this._getType(value);
// check if type corresponds with the provided type
@@ -362,7 +363,7 @@ Node.prototype.setValue = function(value, type) {
value: childValue
});
visible = i < this.MAX_VISIBLE_CHILDS;
- this.appendChild(child, visible);
+ this.appendChild(child, visible, notUpdateDom);
}
}
this.value = '';
@@ -381,7 +382,7 @@ Node.prototype.setValue = function(value, type) {
value: childValue
});
visible = i < this.MAX_VISIBLE_CHILDS;
- this.appendChild(child, visible);
+ this.appendChild(child, visible, notUpdateDom);
}
i++;
}
@@ -398,6 +399,8 @@ Node.prototype.setValue = function(value, type) {
this.childs = undefined;
this.value = value;
}
+
+ this.updateDom({'updateIndexes': true});
this.previousValue = this.value;
};
@@ -664,9 +667,12 @@ Node.prototype.expandTo = function() {
* Add a new child to the node.
* Only applicable when Node value is of type array or object
* @param {Node} node
- * @param {boolean} [visible] If true, the child will be rendered
+ * @param {boolean} [visible] If true (default), the child will be rendered
+ * @param {boolean} [updateDom] If true (default), the DOM of both parent
+ * node and appended node will be updated
+ * (child count, indexes)
*/
-Node.prototype.appendChild = function(node, visible) {
+Node.prototype.appendChild = function(node, visible, updateDom) {
if (this._hasChilds()) {
// adjust the link to the parent
node.setParent(this);
@@ -690,8 +696,10 @@ Node.prototype.appendChild = function(node, visible) {
this.visibleChilds++;
}
- this.updateDom({'updateIndexes': true});
- node.updateDom({'recurse': true});
+ if (updateDom !== false) {
+ this.updateDom({'updateIndexes': true});
+ node.updateDom({'recurse': true});
+ }
}
};
@@ -1105,15 +1113,19 @@ Node.prototype._move = function(node, beforeNode) {
* Remove a child from the node.
* Only applicable when Node value is of type array or object
* @param {Node} node The child node to be removed;
+ * @param {boolean} [updateDom] If true (default), the DOM of the parent
+ * node will be updated (like child count)
* @return {Node | undefined} node The removed node on success,
* else undefined
*/
-Node.prototype.removeChild = function(node) {
+Node.prototype.removeChild = function(node, updateDom) {
if (this.childs) {
var index = this.childs.indexOf(node);
if (index !== -1) {
- this.visibleChilds--;
+ if (index < this.visibleChilds && this.expanded) {
+ this.visibleChilds--;
+ }
node.hide();
@@ -1124,7 +1136,9 @@ Node.prototype.removeChild = function(node) {
var removedNode = this.childs.splice(index, 1)[0];
removedNode.parent = null;
- this.updateDom({'updateIndexes': true});
+ if (updateDom !== false) {
+ this.updateDom({'updateIndexes': true});
+ }
return removedNode;
}
@@ -3147,7 +3161,37 @@ Node.prototype.sort = function (path, direction) {
// update the index numbering
this._updateDomIndexes();
- this.editor._onAction('sort', {
+ this.editor._onAction('transform', {
+ node: this,
+ oldChilds: oldChilds,
+ newChilds: this.childs
+ });
+
+ this.showChilds();
+};
+
+/**
+ * Transform the node given a JMESPath query.
+ * @param {String} query JMESPath query to apply
+ * @private
+ */
+Node.prototype.transform = function (query) {
+ if (!this._hasChilds()) {
+ return;
+ }
+
+ this.hideChilds(); // sorting is faster when the childs are not attached to the dom
+
+ // copy the childs array (the old one will be kept for an undo action
+ var oldChilds = this.childs;
+ this.childs = this.childs.concat();
+
+ // apply the JMESPath query
+ var transformed = jmespath.search(this.getValue(), query);
+
+ this.setValue(transformed);
+
+ this.editor._onAction('transform', {
node: this,
oldChilds: oldChilds,
newChilds: this.childs
@@ -3583,12 +3627,21 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
if (this._hasChilds()) {
items.push({
text: translate('sort'),
- title: translate('sortTitle') + this.type,
+ title: translate('sortTitle', {type: this.type}),
className: 'jsoneditor-sort-asc',
click: function () {
showSortModal(node, node.editor.frame)
}
});
+
+ items.push({
+ text: translate('transform'),
+ title: translate('transformTitle', {type: this.type}),
+ className: 'jsoneditor-transform',
+ click: function () {
+ showTransformModal(node, node.editor.frame)
+ }
+ });
}
if (this.parent && this.parent._hasChilds()) {
diff --git a/src/js/i18n.js b/src/js/i18n.js
index 62a1f9b..1ef3e8b 100644
--- a/src/js/i18n.js
+++ b/src/js/i18n.js
@@ -38,7 +38,7 @@ var _defs = {
'showMore': 'show more',
'showMoreStatus': 'displaying ${visibleChilds} of ${totalChilds} items.',
'sort': 'Sort',
- 'sortTitle': 'Sort the childs of this ',
+ 'sortTitle': 'Sort the childs of this ${type}',
'sortFieldLabel': 'Field:',
'sortDirectionLabel': 'Direction:',
'sortFieldTitle': 'Select the nested field by which to sort the array or object',
@@ -47,6 +47,10 @@ var _defs = {
'sortDescending': 'Descending',
'sortDescendingTitle': 'Sort the selected field in descending order',
'string': 'String',
+ 'transform': 'Transform',
+ 'transformTitle': 'Filter, sort, or transform the childs of this ${type}',
+ 'transformQueryTitle': 'Enter a JMESPath query',
+ 'transformQueryLabel': 'Query',
'type': 'Type',
'typeTitle': 'Change the type of this field',
'openUrl': 'Ctrl+Click or Ctrl+Enter to open url in new window',
@@ -103,7 +107,7 @@ var _defs = {
// TODO: correctly translate showMoreStatus
'showMoreStatus': 'exibindo ${visibleChilds} de ${totalChilds} itens.',
'sort': 'Organizar',
- 'sortTitle': 'Organizar os filhos deste ',
+ 'sortTitle': 'Organizar os filhos deste ${type}',
// TODO: correctly translate sortFieldLabel
'sortFieldLabel': 'Field:',
// TODO: correctly translate sortDirectionLabel
diff --git a/src/js/showTransformModal.js b/src/js/showTransformModal.js
new file mode 100644
index 0000000..35b79d3
--- /dev/null
+++ b/src/js/showTransformModal.js
@@ -0,0 +1,66 @@
+var picoModal = require('picomodal');
+var translate = require('./i18n').translate;
+
+/**
+ * Show advanced filter and transform modal using JMESPath
+ * @param {Node} node the node to be transformed
+ * @param {HTMLElement} container The container where to center
+ * the modal and create an overlay
+ */
+function showTransformModal (node, container) {
+ var content = '
';
+
+ picoModal({
+ parent: container,
+ content: content,
+ overlayClass: 'jsoneditor-modal-overlay',
+ modalClass: 'jsoneditor-modal'
+ })
+ .afterCreate(function (modal) {
+ var form = modal.modalElem().querySelector('form');
+ var ok = modal.modalElem().querySelector('#ok');
+ var query = modal.modalElem().querySelector('#query');
+
+ ok.onclick = function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ modal.close();
+
+ node.transform(query.value)
+ };
+
+ if (form) { // form is not available when JSONEditor is created inside a form
+ form.onsubmit = ok.onclick;
+ }
+ })
+ .afterClose(function (modal) {
+ modal.destroy();
+ })
+ .show();
+}
+
+module.exports = showTransformModal;