From c0f075a9e66a47c9ba77a1e2cd449dc3e93920a2 Mon Sep 17 00:00:00 2001 From: jos Date: Wed, 13 Jul 2016 14:26:11 +0200 Subject: [PATCH] Largely fixed jumping of fields when renaming. Some refactoring. --- public/jsoneditor.css | 6 +- src/JSONNode.js | 6 +- src/Main.js | 19 +++--- src/utils/clone.js | 27 --------- src/utils/getIn.js | 32 ----------- src/utils/isObject.js | 12 ---- src/utils/objectUtils.js | 121 +++++++++++++++++++++++++++++++++++++++ src/utils/setIn.js | 34 ----------- 8 files changed, 136 insertions(+), 121 deletions(-) delete mode 100644 src/utils/clone.js delete mode 100644 src/utils/getIn.js delete mode 100644 src/utils/isObject.js create mode 100644 src/utils/objectUtils.js delete mode 100644 src/utils/setIn.js diff --git a/public/jsoneditor.css b/public/jsoneditor.css index 38d5418..13dfea5 100644 --- a/public/jsoneditor.css +++ b/public/jsoneditor.css @@ -31,7 +31,6 @@ ul.jsoneditor-list { .jsoneditor-value, .jsoneditor-readonly { min-width: 24px; - border-radius: 2px; word-break: break-word; font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif; @@ -40,6 +39,11 @@ ul.jsoneditor-list { outline: none; } +.jsoneditor-field, +.jsoneditor-value { + border-radius: 1px; +} + .jsoneditor-field:focus, .jsoneditor-value:focus { box-shadow: 0 0 3px 1px #008fd5; diff --git a/src/JSONNode.js b/src/JSONNode.js index 992ee74..7e6d1eb 100644 --- a/src/JSONNode.js +++ b/src/JSONNode.js @@ -1,5 +1,5 @@ import { h, Component } from 'preact' -import isObject from './utils/isObject' +import { isObject } from './utils/objectUtils' import { escapeHTML, unescapeHTML } from './utils/escape' import getInnerText from './utils/getInnerText' import stringConvert from './utils/stringConvert' @@ -99,7 +99,7 @@ export default class JSONNode extends Component { class: 'jsoneditor-field' + (hasParent ? '' : ' jsoneditor-readonly'), contentEditable: hasParent, spellCheck: 'false', - onBlur: onChangeField + onInput: onChangeField }, content) } @@ -132,7 +132,7 @@ export default class JSONNode extends Component { const newField = unescapeHTML(getInnerText(event.target)) const oldField = this.props.field if (newField !== oldField) { - this.props.onChangeField(path, newField, oldField) + this.props.onChangeField(path, oldField, newField) } } diff --git a/src/Main.js b/src/Main.js index 0983885..cbd9d07 100644 --- a/src/Main.js +++ b/src/Main.js @@ -1,7 +1,6 @@ import { h, Component } from 'preact' -import setIn from './utils/setIn' -import getIn from './utils/getIn' -import clone from './utils/clone' + +import { getIn, setIn, renameField } from './utils/objectUtils' import JSONNode from './JSONNode' export default class Main extends Component { @@ -37,18 +36,14 @@ export default class Main extends Component { }) } - onChangeField (path, newField, oldField) { + onChangeField (path, oldField, newField) { console.log('onChangeField', path, newField, oldField) + + const oldObject = getIn(this.state.json, path) + const newObject = renameField(oldObject, oldField, newField) - const value = clone(getIn(this.state.json, path)) - - console.log('value', value) - - value[newField] = value[oldField] - delete value[oldField] - this.setState({ - json: setIn(this.state.json, path, value) + json: setIn(this.state.json, path, newObject) }) } diff --git a/src/utils/clone.js b/src/utils/clone.js deleted file mode 100644 index 97fbef0..0000000 --- a/src/utils/clone.js +++ /dev/null @@ -1,27 +0,0 @@ -import isObject from './isObject' - -// TODO: unit test clone - -/** - * Flat clone the properties of an object or array - * @param {Object | Array} value - * @return {Object | Array} Returns a flat clone of the object or Array - */ -export default function clone (value) { - if (Array.isArray(value)) { - return value.slice(0) - } - else if (isObject(value)) { - const cloned = {} - - Object.keys(value).forEach(key => { - cloned[key] = value[key] - }) - - return cloned - } - else { - // a primitive value - return value - } -} diff --git a/src/utils/getIn.js b/src/utils/getIn.js deleted file mode 100644 index 6006606..0000000 --- a/src/utils/getIn.js +++ /dev/null @@ -1,32 +0,0 @@ -import isObject from './isObject' - -// TODO: unit test getIn - -/** - * helper function to get a nested property in an object or array - * - * @param {Object | Array} object - * @param {Array.} path - * @return {* | undefined} Returns the field when found, or undefined when the - * path doesn't exist - */ -export default function getIn (object, path) { - let value = object - let i = 0 - - while(i < path.length) { - if (Array.isArray(value) || isObject(value)) { - value = value[path[i]] - } - else { - value = undefined - } - - i++ - } - - return value -} - - -window.getIn = getIn // TODO: cleanup diff --git a/src/utils/isObject.js b/src/utils/isObject.js deleted file mode 100644 index 0790930..0000000 --- a/src/utils/isObject.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Test whether a value is an object (and not an Array or Date or primitive value) - * - * @param {*} value - * @return {boolean} - */ -export default function isObject (value) { - return typeof value === 'object' && - value && // not null - !Array.isArray(value) && - value.toString() === '[object Object]' -} \ No newline at end of file diff --git a/src/utils/objectUtils.js b/src/utils/objectUtils.js new file mode 100644 index 0000000..5cb43b4 --- /dev/null +++ b/src/utils/objectUtils.js @@ -0,0 +1,121 @@ + +/** + * Test whether a value is an object (and not an Array or Date or primitive value) + * + * @param {*} value + * @return {boolean} + */ +export function isObject (value) { + return typeof value === 'object' && + value && // not null + !Array.isArray(value) && + value.toString() === '[object Object]' +} + +// TODO: unit test getIn + +/** + * Flat clone the properties of an object or array + * @param {Object | Array} value + * @return {Object | Array} Returns a flat clone of the object or Array + */ +export function clone (value) { + if (Array.isArray(value)) { + return value.slice(0) + } + else if (isObject(value)) { + const cloned = {} + + Object.keys(value).forEach(key => { + cloned[key] = value[key] + }) + + return cloned + } + else { + // a primitive value + return value + } +} + +/** + * helper function to get a nested property in an object or array + * + * @param {Object | Array} object + * @param {Array.} path + * @return {* | undefined} Returns the field when found, or undefined when the + * path doesn't exist + */ +export function getIn (object, path) { + let value = object + let i = 0 + + while(i < path.length) { + if (Array.isArray(value) || isObject(value)) { + value = value[path[i]] + } + else { + value = undefined + } + + i++ + } + + return value +} + +// TODO: unit test setIn + +/** + * helper function to replace a nested property in an object with a new value + * without mutating the object itself. + * + * @param {Object | Array} object + * @param {Array.} path + * @param {*} value + * @return {Object | Array} Returns a new, updated object or array + */ +export function setIn (object, path, value) { + if (path.length === 0) { + return value + } + + // TODO: change array into object and vice versa when key is a number/string + + const key = path[0] + const child = (Array.isArray(object[key]) || isObject(object[key])) + ? object[key] + : (typeof path[1] === 'number' ? [] : {}) + const updated = clone(object) + + updated[key] = setIn(child, path.slice(1), value) + + return updated +} + +/** + * Rename a field in an object without mutating the object itself. + * The order of the fields in the object is maintained. + * @param {Object} object + * @param {string} oldField + * @param {string} newField + * @return {Object} Returns a clone of the object where property `oldField` is + * renamed to `newField` + */ +export function renameField(object, oldField, newField) { + const renamed = {} + + // important: maintain the order in which we add the properties to newValue, + // else the field will "jump" to another place + Object.keys(object).forEach(field => { + if (field === oldField) { + renamed[newField] = object[oldField] + } + else { + renamed[field] = object[field] + } + }) + + return renamed +} + diff --git a/src/utils/setIn.js b/src/utils/setIn.js deleted file mode 100644 index 50a2a7a..0000000 --- a/src/utils/setIn.js +++ /dev/null @@ -1,34 +0,0 @@ -import isObject from './isObject' -import clone from './clone' - -// TODO: unit test setIn - -/** - * helper function to replace a nested property in an object with a new value - * without mutating the object itself. - * - * @param {Object | Array} object - * @param {Array.} path - * @param {*} value - * @return {Object | Array} Returns a new, updated object or array - */ -export default function setIn (object, path, value) { - if (path.length === 0) { - return value - } - - // TODO: change array into object and vice versa when key is a number/string - - const key = path[0] - const child = (Array.isArray(object[key]) || isObject(object[key])) - ? object[key] - : (typeof path[1] === 'number' ? [] : {}) - const updated = clone(object) - - updated[key] = setIn(child, path.slice(1), value) - - return updated -} - - -window.setIn = setIn // TODO: cleanup