Implemented advanced sorting modal (WIP)

This commit is contained in:
jos 2018-05-30 21:54:25 +02:00
parent 9eb9c4fe0b
commit 8b77111393
7 changed files with 190 additions and 45 deletions

View File

@ -3,6 +3,11 @@
https://github.com/josdejong/jsoneditor https://github.com/josdejong/jsoneditor
## not yet released, version 5.17.0
- Implemented advanced sorting for arrays.
## 2018-05-23, version 5.16.0 ## 2018-05-23, version 5.16.0
- Better handling of JSON documents containing large arrays: - Better handling of JSON documents containing large arrays:

5
package-lock.json generated
View File

@ -3676,6 +3676,11 @@
"integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=", "integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=",
"dev": true "dev": true
}, },
"picomodal": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/picomodal/-/picomodal-3.0.0.tgz",
"integrity": "sha1-+s0w9PvzSoCcHgTqUl8ATzmcC4I="
},
"posix-character-classes": { "posix-character-classes": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",

View File

@ -25,7 +25,8 @@
"dependencies": { "dependencies": {
"ajv": "5.5.2", "ajv": "5.5.2",
"brace": "0.11.0", "brace": "0.11.0",
"javascript-natural-sort": "0.7.1" "javascript-natural-sort": "0.7.1",
"picomodal": "3.0.0"
}, },
"devDependencies": { "devDependencies": {
"gulp": "3.9.1", "gulp": "3.9.1",

View File

@ -260,3 +260,47 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
background-image: none; background-image: none;
width: 6px; width: 6px;
} }
/* pico modal styling */
.jsoneditor-modal-overlay {
position: absolute !important;
background: rgb(1,1,1) !important;
opacity: 0.3 !important;
}
.jsoneditor-modal {
position: absolute !important;
border-radius: 2px !important;
padding: 30px 20px 10px 20px !important;
}
.jsoneditor-modal table td {
padding: 5px 0;
padding-right: 20px;
text-align: left;
vertical-align: middle;
font-size: 10pt;
font-family: arial, sans-serif;
}
.jsoneditor-modal table td:first-child {
}
.jsoneditor-modal table td.jsoneditor-modal-input {
text-align: right;
padding-right: 0;
}
.jsoneditor-modal select {
min-width: 100px;
}
.jsoneditor-modal .pico-close {
background: none !important;
font-size: 24px !important;
}

View File

@ -124,15 +124,15 @@ function History (editor) {
'undo': function (params) { 'undo': function (params) {
var node = params.node; var node = params.node;
node.hideChilds(); node.hideChilds();
node.sort = params.oldSort;
node.childs = params.oldChilds; node.childs = params.oldChilds;
node._updateDomIndexes();
node.showChilds(); node.showChilds();
}, },
'redo': function (params) { 'redo': function (params) {
var node = params.node; var node = params.node;
node.hideChilds(); node.hideChilds();
node.sort = params.newSort;
node.childs = params.newChilds; node.childs = params.newChilds;
node._updateDomIndexes();
node.showChilds(); node.showChilds();
} }
} }

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
var naturalSort = require('javascript-natural-sort'); var naturalSort = require('javascript-natural-sort');
var picoModal = require('picomodal');
var ContextMenu = require('./ContextMenu'); var ContextMenu = require('./ContextMenu');
var appendNodeFactory = require('./appendNodeFactory'); var appendNodeFactory = require('./appendNodeFactory');
var showMoreNodeFactory = require('./showMoreNodeFactory'); var showMoreNodeFactory = require('./showMoreNodeFactory');
@ -389,7 +390,7 @@ Node.prototype.setValue = function(value, type) {
// sort object keys // sort object keys
if (this.editor.options.sortObjectKeys === true) { if (this.editor.options.sortObjectKeys === true) {
this.sort('asc'); this.sort([], 'asc');
} }
} }
else { else {
@ -3096,29 +3097,42 @@ Node.prototype._onChangeType = function (newType) {
/** /**
* Sort the child's of the node. Only applicable when the node has type 'object' * Sort the child's of the node. Only applicable when the node has type 'object'
* or 'array'. * or 'array'.
* @param {String[]} path Path of the child value to be compared
* @param {String} direction Sorting direction. Available values: "asc", "desc" * @param {String} direction Sorting direction. Available values: "asc", "desc"
* @private * @private
*/ */
Node.prototype.sort = function (direction) { Node.prototype.sort = function (path, direction) {
if (!this._hasChilds()) { if (!this._hasChilds()) {
return; return;
} }
var order = (direction == 'desc') ? -1 : 1; this.hideChilds(); // sorting is faster when the childs are not attached to the dom
var prop = (this.type == 'array') ? 'value': 'field';
this.hideChilds();
// copy the childs array (the old one will be kept for an undo action
var oldChilds = this.childs; var oldChilds = this.childs;
var oldSortOrder = this.sortOrder;
// copy the array (the old one will be kept for an undo action
this.childs = this.childs.concat(); this.childs = this.childs.concat();
// sort the arrays // sort the childs array
this.childs.sort(function (a, b) { var order = (direction === 'desc') ? -1 : 1;
return order * naturalSort(a[prop], b[prop]);
}); if (this.type === 'object') {
this.sortOrder = (order == 1) ? 'asc' : 'desc'; this.childs.sort(function (a, b) {
return order * naturalSort(a.field, b.field);
});
}
else { // this.type === 'array'
this.childs.sort(function (a, b) {
var valueA = a.getNestedChild(path).value;
var valueB = b.getNestedChild(path).value;
if (typeof valueA !== 'string' && typeof valueB !== 'string') {
// both values are a number, boolean, or null
return valueA > valueB ? order : valueA < valueB ? -order : 0;
}
return order * naturalSort(valueA, valueB);
});
}
// update the index numbering // update the index numbering
this._updateDomIndexes(); this._updateDomIndexes();
@ -3126,14 +3140,44 @@ Node.prototype.sort = function (direction) {
this.editor._onAction('sort', { this.editor._onAction('sort', {
node: this, node: this,
oldChilds: oldChilds, oldChilds: oldChilds,
oldSort: oldSortOrder, newChilds: this.childs
newChilds: this.childs,
newSort: this.sortOrder
}); });
this.showChilds(); this.showChilds();
}; };
/**
* Get a nested child given a path with properties
* @param {String[]} path
* @returns {Node}
*/
Node.prototype.getNestedChild = function (path) {
var i = 0;
var child = this;
while (child && i < path.length) {
child = child.findChildByProperty(path[i]);
i++;
}
return child;
};
/**
* Find a child by property name
* @param {string} prop
* @return {Node | undefined} Returns the child node when found, or undefined otherwise
*/
Node.prototype.findChildByProperty = function(prop) {
if (this.type !== 'object') {
return undefined;
}
return this.childs.find(function (child) {
return child.field === prop;
});
};
/** /**
* Get the paths of the sortable child paths of this node * Get the paths of the sortable child paths of this node
* @return {Array} * @return {Array}
@ -3530,32 +3574,13 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
} }
if (this._hasChilds()) { if (this._hasChilds()) {
var direction = ((this.sortOrder == 'asc') ? 'desc': 'asc');
items.push({ items.push({
text: translate('sort'), text: translate('sort'),
title: translate('sortTitle') + this.type, title: translate('sortTitle') + this.type,
className: 'jsoneditor-sort-' + direction, className: 'jsoneditor-sort-asc',
click: function () { click: function () {
node.sort(direction); node._showSortModal()
}, }
submenu: [
{
text: translate('ascending'),
className: 'jsoneditor-sort-asc',
title: translate('ascendingTitle' , {type: this.type}),
click: function () {
node.sort('asc');
}
},
{
text: translate('descending'),
className: 'jsoneditor-sort-desc',
title: translate('descendingTitle' , {type: this.type}),
click: function () {
node.sort('desc');
}
}
]
}); });
} }
@ -3693,6 +3718,72 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
menu.show(anchor, this.editor.content); menu.show(anchor, this.editor.content);
}; };
/**
* Show advanced sorting modal
* @private
*/
Node.prototype._showSortModal = function () {
var node = this;
picoModal({
parent: this.editor.frame,
content: '<div class="pico-modal-contents">' +
'<form onsubmit="alert(1)">' +
'<table>' +
'<tbody>' +
'<tr>' +
' <td>Sort by:</td>' +
' <td class="jsoneditor-modal-input">' +
' <select id="sortBy">' +
' </select>' +
' </td>' +
'</tr>' +
' <tr>' +
' <td>Direction:</td>' +
' <td class="jsoneditor-modal-input">' +
' <select id="direction">' +
' <option value="asc" selected>asc</option>' +
' <option value="desc">desc</option>' +
' </select>' +
' </td>' +
'</tr>' +
'<tr>' +
'<td colspan="2" class="jsoneditor-modal-input">' +
' <button type="submit" id="ok">Sort</button>' +
'</td>' +
'</tr>' +
'</tbody>' +
'</table>' +
'</form>' +
'</div>',
overlayClass: 'jsoneditor-modal-overlay',
modalClass: 'jsoneditor-modal'
})
.afterCreate(function (modal) {
var sortBy = modal.modalElem().querySelector('#sortBy');
var direction = modal.modalElem().querySelector('#direction');
var ok = modal.modalElem().querySelector('#ok');
node.getSortablePaths().forEach(function (path) {
var pathStr = '.' + path.join('.');
var option = document.createElement('option');
option.text = pathStr;
option.value = pathStr;
sortBy.appendChild(option);
});
ok.onclick = function () {
modal.close();
var path = sortBy.value;
var pathArray = (path === '.') ? [] : path.split('.').slice(1);
node.sort(pathArray, direction.value)
};
})
.show();
};
/** /**
* get the type of a value * get the type of a value
* @param {*} value * @param {*} value

View File

@ -33,9 +33,7 @@
<code>editor.setMode(mode)</code>, try it in the console of your browser. <code>editor.setMode(mode)</code>, try it in the console of your browser.
</p> </p>
<form> <div id="jsoneditor"></div>
<div id="jsoneditor"></div>
</form>
<script> <script>
var container = document.getElementById('jsoneditor'); var container = document.getElementById('jsoneditor');
@ -70,7 +68,8 @@
location: { location: {
latitude: 51 + i / 10000, latitude: 51 + i / 10000,
longitude: 4 + i / 10000, longitude: 4 + i / 10000,
} },
random: Math.random()
}); });
} }