diff --git a/HISTORY.md b/HISTORY.md index 273634c..939a41d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,11 @@ https://github.com/josdejong/jsoneditor +## not yet released, version 5.17.0 + +- Implemented advanced sorting for arrays. + + ## 2018-05-23, version 5.16.0 - Better handling of JSON documents containing large arrays: diff --git a/package-lock.json b/package-lock.json index 40dffcb..b871020 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3676,6 +3676,11 @@ "integrity": "sha1-tuDI+plJTZTgURV1gCpZpcFC8og=", "dev": true }, + "picomodal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/picomodal/-/picomodal-3.0.0.tgz", + "integrity": "sha1-+s0w9PvzSoCcHgTqUl8ATzmcC4I=" + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", diff --git a/package.json b/package.json index 2a4a3f1..463ee51 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "dependencies": { "ajv": "5.5.2", "brace": "0.11.0", - "javascript-natural-sort": "0.7.1" + "javascript-natural-sort": "0.7.1", + "picomodal": "3.0.0" }, "devDependencies": { "gulp": "3.9.1", diff --git a/src/css/contextmenu.css b/src/css/contextmenu.css index 6f693e2..88f8225 100644 --- a/src/css/contextmenu.css +++ b/src/css/contextmenu.css @@ -260,3 +260,47 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { background-image: none; 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; +} \ No newline at end of file diff --git a/src/js/History.js b/src/js/History.js index fa4c9e9..a363700 100644 --- a/src/js/History.js +++ b/src/js/History.js @@ -124,15 +124,15 @@ function History (editor) { 'undo': function (params) { var node = params.node; node.hideChilds(); - node.sort = params.oldSort; node.childs = params.oldChilds; + node._updateDomIndexes(); node.showChilds(); }, 'redo': function (params) { var node = params.node; node.hideChilds(); - node.sort = params.newSort; node.childs = params.newChilds; + node._updateDomIndexes(); node.showChilds(); } } diff --git a/src/js/Node.js b/src/js/Node.js index b852abb..b0692c3 100644 --- a/src/js/Node.js +++ b/src/js/Node.js @@ -1,6 +1,7 @@ 'use strict'; var naturalSort = require('javascript-natural-sort'); +var picoModal = require('picomodal'); var ContextMenu = require('./ContextMenu'); var appendNodeFactory = require('./appendNodeFactory'); var showMoreNodeFactory = require('./showMoreNodeFactory'); @@ -389,7 +390,7 @@ Node.prototype.setValue = function(value, type) { // sort object keys if (this.editor.options.sortObjectKeys === true) { - this.sort('asc'); + this.sort([], 'asc'); } } 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' * or 'array'. + * @param {String[]} path Path of the child value to be compared * @param {String} direction Sorting direction. Available values: "asc", "desc" * @private */ -Node.prototype.sort = function (direction) { +Node.prototype.sort = function (path, direction) { if (!this._hasChilds()) { return; } - var order = (direction == 'desc') ? -1 : 1; - var prop = (this.type == 'array') ? 'value': 'field'; - this.hideChilds(); + 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; - var oldSortOrder = this.sortOrder; - - // copy the array (the old one will be kept for an undo action this.childs = this.childs.concat(); - // sort the arrays - this.childs.sort(function (a, b) { - return order * naturalSort(a[prop], b[prop]); - }); - this.sortOrder = (order == 1) ? 'asc' : 'desc'; + // sort the childs array + var order = (direction === 'desc') ? -1 : 1; + + if (this.type === 'object') { + 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 this._updateDomIndexes(); @@ -3126,14 +3140,44 @@ Node.prototype.sort = function (direction) { this.editor._onAction('sort', { node: this, oldChilds: oldChilds, - oldSort: oldSortOrder, - newChilds: this.childs, - newSort: this.sortOrder + newChilds: this.childs }); 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 * @return {Array} @@ -3530,32 +3574,13 @@ Node.prototype.showContextMenu = function (anchor, onClose) { } if (this._hasChilds()) { - var direction = ((this.sortOrder == 'asc') ? 'desc': 'asc'); items.push({ text: translate('sort'), title: translate('sortTitle') + this.type, - className: 'jsoneditor-sort-' + direction, + className: 'jsoneditor-sort-asc', click: function () { - node.sort(direction); - }, - 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'); - } - } - ] + node._showSortModal() + } }); } @@ -3693,6 +3718,72 @@ Node.prototype.showContextMenu = function (anchor, onClose) { menu.show(anchor, this.editor.content); }; +/** + * Show advanced sorting modal + * @private + */ +Node.prototype._showSortModal = function () { + var node = this; + + picoModal({ + parent: this.editor.frame, + content: '
' + + '
' + + '' + + '' + + '' + + ' ' + + ' ' + + '' + + ' ' + + ' ' + + ' ' + + '' + + '' + + '' + + '' + + '' + + '
Sort by:' + + ' ' + + '
Direction:' + + ' ' + + '
' + + ' ' + + '
' + + '
' + + '
', + 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 * @param {*} value diff --git a/test/test_large_array.html b/test/test_large_array.html index 40e9e1d..2ddf91c 100644 --- a/test/test_large_array.html +++ b/test/test_large_array.html @@ -33,9 +33,7 @@ editor.setMode(mode), try it in the console of your browser.

-
-
-
+