Implemented sort button in code/text mode

This commit is contained in:
jos 2019-06-19 11:16:30 +02:00
parent 12dc0408e8
commit 74b816a554
7 changed files with 402 additions and 21 deletions

View File

@ -4292,8 +4292,7 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
title: translate('sortTitle', {type: this.type}), title: translate('sortTitle', {type: this.type}),
className: 'jsoneditor-sort-asc', className: 'jsoneditor-sort-asc',
click: function () { click: function () {
var anchor = node.editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR; node.showSortModal()
showSortModal(node, anchor)
} }
}); });
} }
@ -4455,6 +4454,31 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
menu.show(anchor, this.editor.frame); menu.show(anchor, this.editor.frame);
}; };
/**
* Show advanced sorting modal
*/
Node.prototype.showSortModal = function () {
var node = this;
var container = this.editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR;
var paths = node.type === 'array'
? node.getChildPaths()
: ['.'];
showSortModal(container, {
paths: paths,
path: node.sortedBy ? node.sortedBy.path : paths[0],
direction: node.sortedBy ? node.sortedBy.direction : 'asc',
onSort: function (sortedBy) {
var path = sortedBy.path;
var pathArray = (path === '.') ? [] : path.split('.').slice(1);
node.sortedBy = sortedBy
node.sort(pathArray, sortedBy.direction)
}
})
}
/** /**
* get the type of a value * get the type of a value
* @param {*} value * @param {*} value

View File

@ -3,11 +3,24 @@ var translate = require('./i18n').translate;
/** /**
* Show advanced sorting modal * Show advanced sorting modal
* @param {Node} node the node to be sorted
* @param {HTMLElement} container The container where to center * @param {HTMLElement} container The container where to center
* the modal and create an overlay * the modal and create an overlay
* @param {Object} options
* Available options:
* - {Array} paths The available paths
* - {string} path The selected path
* - {'asc' | 'desc'} direction The selected direction
* - {function} onSort Callback function,
* invoked with an object
* containing the selected
* path and direction
*/ */
function showSortModal (node, container) { function showSortModal (container, options) {
var paths = options && options.paths || ['.']
var selectedPath = options && options.path || paths[0]
var selectedDirection = options && options.direction || 'asc'
var onSort = options && options.onSort || function () {}
var content = '<div class="pico-modal-contents">' + var content = '<div class="pico-modal-contents">' +
'<div class="pico-modal-header">' + translate('sort') + '</div>' + '<div class="pico-modal-header">' + translate('sort') + '</div>' +
'<form>' + '<form>' +
@ -61,10 +74,6 @@ function showSortModal (node, container) {
var field = modal.modalElem().querySelector('#field'); var field = modal.modalElem().querySelector('#field');
var direction = modal.modalElem().querySelector('#direction'); var direction = modal.modalElem().querySelector('#direction');
var paths = node.type === 'array'
? node.getChildPaths()
: ['.'];
paths.forEach(function (path) { paths.forEach(function (path) {
var option = document.createElement('option'); var option = document.createElement('option');
option.text = path; option.text = path;
@ -77,8 +86,8 @@ function showSortModal (node, container) {
direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value; direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value;
} }
field.value = node.sortedBy ? node.sortedBy.path : paths[0]; field.value = selectedPath || paths[0];
setDirection(node.sortedBy ? node.sortedBy.direction : 'asc'); setDirection(selectedDirection || 'asc');
direction.onclick = function (event) { direction.onclick = function (event) {
setDirection(event.target.getAttribute('data-value')); setDirection(event.target.getAttribute('data-value'));
@ -90,15 +99,10 @@ function showSortModal (node, container) {
modal.close(); modal.close();
var path = field.value; onSort({
var pathArray = (path === '.') ? [] : path.split('.').slice(1); path: field.value,
node.sortedBy = {
path: path,
direction: direction.value direction: direction.value
}; })
node.sort(pathArray, direction.value)
}; };
if (form) { // form is not available when JSONEditor is created inside a form if (form) { // form is not available when JSONEditor is created inside a form

View File

@ -1,13 +1,17 @@
'use strict'; 'use strict';
var ace = require('./ace'); var ace = require('./ace');
var translate = require('./i18n').translate;
var ModeSwitcher = require('./ModeSwitcher'); var ModeSwitcher = require('./ModeSwitcher');
var showSortModal = require('./showSortModal');
var showTransformModal = require('./showTransformModal');
var util = require('./util'); var util = require('./util');
// create a mixin with the functions for text mode // create a mixin with the functions for text mode
var textmode = {}; var textmode = {};
var DEFAULT_THEME = 'ace/theme/jsoneditor'; var DEFAULT_THEME = 'ace/theme/jsoneditor';
var DEFAULT_MODAL_ANCHOR = document.body; // TODO: this constant is defined multiple times
/** /**
* Create a text editor * Create a text editor
@ -47,6 +51,8 @@ textmode.create = function (container, options) {
// setting default for textmode // setting default for textmode
options.mainMenuBar = options.mainMenuBar !== false; options.mainMenuBar = options.mainMenuBar !== false;
options.enableSort = options.enableSort !== false;
options.enableTransform = options.enableTransform !== false;
this.options = options; this.options = options;
@ -160,6 +166,32 @@ textmode.create = function (container, options) {
} }
}; };
// create sort button
if (this.options.enableSort) {
var sort = document.createElement('button');
sort.type = 'button';
sort.className = 'jsoneditor-sort';
sort.title = translate('sortTitleShort');
sort.onclick = function () {
me._showSortModal()
};
this.menu.appendChild(sort);
}
// TODO
// // create transform button
// if (this.options.enableTransform) {
// var transform = document.createElement('button');
// transform.type = 'button';
// transform.title = translate('transformTitleShort');
// transform.className = 'jsoneditor-transform';
// transform.onclick = function () {
// var anchor = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR;
// showTransformModal(me.node, anchor)
// };
// this.menu.appendChild(transform);
// }
// create repair button // create repair button
var buttonRepair = document.createElement('button'); var buttonRepair = document.createElement('button');
buttonRepair.type = 'button'; buttonRepair.type = 'button';
@ -400,6 +432,42 @@ textmode._onChange = function () {
} }
}; };
/**
* Open a sort modal
* @private
*/
textmode._showSortModal = function () {
var me = this;
var container = this.options.modalAnchor || DEFAULT_MODAL_ANCHOR;
var json = this.get()
var paths = Array.isArray(json)
? util.getChildPaths(json)
: ['.'];
showSortModal(container, {
paths: paths,
path: (me.sortedBy && util.contains(paths, me.sortedBy.path))
? me.sortedBy.path
: paths[0],
direction: me.sortedBy ? me.sortedBy.direction : 'asc',
onSort: function (sortedBy) {
if (Array.isArray(json)) {
var sortedJson = util.sort(json, sortedBy.path, sortedBy.direction);
me.sortedBy = sortedBy
me.set(sortedJson);
}
if (util.isObject(json)) {
var sortedJson = util.sortObjectKeys(json, sortedBy.direction);
me.sortedBy = sortedBy;
me.set(sortedJson);
}
}
})
}
/** /**
* Handle text selection * Handle text selection
* Calculates the cursor position and selection range and updates menu * Calculates the cursor position and selection range and updates menu

View File

@ -10,7 +10,6 @@ var Node = require('./Node');
var ModeSwitcher = require('./ModeSwitcher'); var ModeSwitcher = require('./ModeSwitcher');
var util = require('./util'); var util = require('./util');
var autocomplete = require('./autocomplete'); var autocomplete = require('./autocomplete');
var showSortModal = require('./showSortModal');
var showTransformModal = require('./showTransformModal'); var showTransformModal = require('./showTransformModal');
var translate = require('./i18n').translate; var translate = require('./i18n').translate;
var setLanguages = require('./i18n').setLanguages; var setLanguages = require('./i18n').setLanguages;
@ -1022,8 +1021,7 @@ treemode._createFrame = function () {
sort.className = 'jsoneditor-sort'; sort.className = 'jsoneditor-sort';
sort.title = translate('sortTitleShort'); sort.title = translate('sortTitleShort');
sort.onclick = function () { sort.onclick = function () {
var anchor = editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR; editor.node.showSortModal()
showSortModal(editor.node, anchor)
}; };
this.menu.appendChild(sort); this.menu.appendChild(sort);
} }

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
require('./polyfills'); require('./polyfills');
var naturalSort = require('javascript-natural-sort');
var jsonlint = require('./assets/jsonlint/jsonlint'); var jsonlint = require('./assets/jsonlint/jsonlint');
var jsonMap = require('json-source-map'); var jsonMap = require('json-source-map');
var translate = require('./i18n').translate; var translate = require('./i18n').translate;
@ -1187,3 +1188,97 @@ exports.findUniqueName = function(name, existingPropNames) {
return validName return validName
} }
/**
* Get the child paths of an array
* @param {JSON} json
* @param {boolean} [includeObjects=false] If true, object and array paths are returned as well
* @return {string[]}
*/
exports.getChildPaths = function (json, includeObjects) {
var pathsMap = {};
function getObjectChildPaths (json, pathsMap, rootPath, includeObjects) {
var isValue = !Array.isArray(json) && !exports.isObject(json)
if (isValue || includeObjects) {
pathsMap[rootPath || '.'] = true;
}
if (exports.isObject(json)) {
Object.keys(json).forEach(function (field) {
getObjectChildPaths(json[field], pathsMap, rootPath + '.' + field, includeObjects);
});
}
}
if (Array.isArray(json)) {
json.forEach(function (item) {
getObjectChildPaths(item, pathsMap, '', includeObjects);
});
}
else {
pathsMap['.'] = true;
}
return Object.keys(pathsMap).sort();
}
/**
* Sort object keys using natural sort
* @param {Array} array
* @param {String} [path] JSON pointer
* @param {'asc' | 'desc'} [direction]
*/
exports.sort = function (array, path, direction) {
var parsedPath = path && path !== '.' ? exports.parsePath(path) : []
var sign = direction === 'desc' ? -1: 1
var sortedArray = array.slice()
sortedArray.sort(function (a, b) {
const aValue = exports.get(a, parsedPath);
const bValue = exports.get(b, parsedPath);
return sign * (aValue > bValue ? 1 : aValue < bValue ? -1 : 0);
})
return sortedArray;
}
/**
* Sort object keys using natural sort
* @param {Object} object
* @param {'asc' | 'desc'} [direction]
*/
exports.sortObjectKeys = function (object, direction) {
var sign = (direction === 'desc') ? -1 : 1;
var sortedFields = Object.keys(object).sort(function (a, b) {
return sign * naturalSort(a, b);
});
var sortedObject = {};
sortedFields.forEach(function (field) {
sortedObject[field] = object[field];
});
return sortedObject;
}
/**
* Test whether a value is an Object
* @param {*} value
* @return {boolean}
*/
exports.isObject = function (value) {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
/**
* Helper function to test whether an array contains an item
* @param {Array} array
* @param {*} item
* @return {boolean} Returns true if `item` is in `array`, returns false otherwise.
*/
exports.contains = function (array, item) {
return array.indexOf(item) !== -1;
}

89
test/test_code_mode.html Normal file
View File

@ -0,0 +1,89 @@
<!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, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'code',
modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
onError: function (err) {
alert(err.toString());
},
onChange: function () {
console.log('change');
},
onChangeJSON: function (json) {
console.log('onChangeJSON', json);
},
onChangeText: function (text) {
console.log('onChangeText', text);
},
indentation: 4,
escapeUnicode: true
};
var json = [];
for (var i = 0; i < 10000; i++) {
var longitude = 4 + i / 10000;
var latitude = 51 + i / 10000;
json.push({
name: 'Item ' + i,
id: String(i),
index: i,
time: new Date().toISOString(),
location: {
latitude: longitude,
longitude: latitude,
coordinates: [longitude, latitude]
},
random: Math.random()
});
}
editorTest = new JSONEditor(container, options, json);
// console.log('json', json);
console.log('stringified size: ', Math.round(JSON.stringify(json).length / 1024 / 1024), 'MB');
</script>
</body>
</html>

View File

@ -250,6 +250,109 @@ describe('util', function () {
}); });
}); });
describe('getChildPaths', function () {
it('should extract all child paths of an array containing objects', function () {
var json = [
{ name: 'A', location: {latitude: 1, longitude: 2} },
{ name: 'B', location: {latitude: 1, longitude: 2} },
{ name: 'C', timestamp: 0 },
];
assert.deepStrictEqual(util.getChildPaths(json), [
'.location.latitude',
'.location.longitude',
'.name',
'.timestamp',
])
});
it('should extract all child paths of an array containing objects, including objects', function () {
var json = [
{ name: 'A', location: {latitude: 1, longitude: 2} },
{ name: 'B', location: {latitude: 1, longitude: 2} },
{ name: 'C', timestamp: 0 },
];
assert.deepStrictEqual(util.getChildPaths(json, true), [
'.',
'.location',
'.location.latitude',
'.location.longitude',
'.name',
'.timestamp',
])
});
it('should extract all child paths of an array containing values', function () {
var json = [ 1, 2, 3 ];
assert.deepStrictEqual(util.getChildPaths(json), [
'.'
])
});
it('should extract all child paths of a non-array', function () {
assert.deepStrictEqual(util.getChildPaths({a: 2, b: {c: 3}}), ['.'])
assert.deepStrictEqual(util.getChildPaths('foo'), ['.'])
assert.deepStrictEqual(util.getChildPaths(123), ['.'])
});
})
it('should test whether something is an object', function () {
assert.strictEqual(util.isObject({}), true);
assert.strictEqual(util.isObject(new Date()), true);
assert.strictEqual(util.isObject([]), false);
assert.strictEqual(util.isObject(2), false);
assert.strictEqual(util.isObject(null), false);
assert.strictEqual(util.isObject(undefined), false);
assert.strictEqual(util.isObject(), false);
});
describe('sort', function () {
it('should sort an array', function () {
var array = [4, 1, 10, 2];
assert.deepStrictEqual(util.sort(array), [1, 2, 4, 10]);
assert.deepStrictEqual(util.sort(array, '.', 'desc'), [10, 4, 2, 1]);
});
it('should sort an array containing objects', function () {
var array = [
{ value: 4 },
{ value: 1 },
{ value: 10 },
{ value: 2 }
];
assert.deepStrictEqual(util.sort(array, '.value'), [
{ value: 1 },
{ value: 2 },
{ value: 4 },
{ value: 10 }
]);
assert.deepStrictEqual(util.sort(array, '.value', 'desc'), [
{ value: 10 },
{ value: 4 },
{ value: 2 },
{ value: 1 }
]);
});
});
describe('sortObjectKeys', function () {
it('should sort the keys of an object', function () {
var object = {
c: 'c',
a: 'a',
b: 'b'
}
assert.strictEqual(JSON.stringify(object), '{"c":"c","a":"a","b":"b"}')
assert.strictEqual(JSON.stringify(util.sortObjectKeys(object)), '{"a":"a","b":"b","c":"c"}')
assert.strictEqual(JSON.stringify(util.sortObjectKeys(object, 'asc')), '{"a":"a","b":"b","c":"c"}')
assert.strictEqual(JSON.stringify(util.sortObjectKeys(object, 'desc')), '{"c":"c","b":"b","a":"a"}')
});
});
it('should find a unique name', function () { it('should find a unique name', function () {
assert.strictEqual(util.findUniqueName('other', [ assert.strictEqual(util.findUniqueName('other', [
'a', 'a',