Largely fixed jumping of fields when renaming. Some refactoring.
This commit is contained in:
parent
8723855bdf
commit
c0f075a9e6
|
@ -31,7 +31,6 @@ ul.jsoneditor-list {
|
||||||
.jsoneditor-value,
|
.jsoneditor-value,
|
||||||
.jsoneditor-readonly {
|
.jsoneditor-readonly {
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
border-radius: 2px;
|
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
|
||||||
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
|
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
|
||||||
|
@ -40,6 +39,11 @@ ul.jsoneditor-list {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jsoneditor-field,
|
||||||
|
.jsoneditor-value {
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.jsoneditor-field:focus,
|
.jsoneditor-field:focus,
|
||||||
.jsoneditor-value:focus {
|
.jsoneditor-value:focus {
|
||||||
box-shadow: 0 0 3px 1px #008fd5;
|
box-shadow: 0 0 3px 1px #008fd5;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { h, Component } from 'preact'
|
import { h, Component } from 'preact'
|
||||||
import isObject from './utils/isObject'
|
import { isObject } from './utils/objectUtils'
|
||||||
import { escapeHTML, unescapeHTML } from './utils/escape'
|
import { escapeHTML, unescapeHTML } from './utils/escape'
|
||||||
import getInnerText from './utils/getInnerText'
|
import getInnerText from './utils/getInnerText'
|
||||||
import stringConvert from './utils/stringConvert'
|
import stringConvert from './utils/stringConvert'
|
||||||
|
@ -99,7 +99,7 @@ export default class JSONNode extends Component {
|
||||||
class: 'jsoneditor-field' + (hasParent ? '' : ' jsoneditor-readonly'),
|
class: 'jsoneditor-field' + (hasParent ? '' : ' jsoneditor-readonly'),
|
||||||
contentEditable: hasParent,
|
contentEditable: hasParent,
|
||||||
spellCheck: 'false',
|
spellCheck: 'false',
|
||||||
onBlur: onChangeField
|
onInput: onChangeField
|
||||||
}, content)
|
}, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ export default class JSONNode extends Component {
|
||||||
const newField = unescapeHTML(getInnerText(event.target))
|
const newField = unescapeHTML(getInnerText(event.target))
|
||||||
const oldField = this.props.field
|
const oldField = this.props.field
|
||||||
if (newField !== oldField) {
|
if (newField !== oldField) {
|
||||||
this.props.onChangeField(path, newField, oldField)
|
this.props.onChangeField(path, oldField, newField)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
src/Main.js
19
src/Main.js
|
@ -1,7 +1,6 @@
|
||||||
import { h, Component } from 'preact'
|
import { h, Component } from 'preact'
|
||||||
import setIn from './utils/setIn'
|
|
||||||
import getIn from './utils/getIn'
|
import { getIn, setIn, renameField } from './utils/objectUtils'
|
||||||
import clone from './utils/clone'
|
|
||||||
import JSONNode from './JSONNode'
|
import JSONNode from './JSONNode'
|
||||||
|
|
||||||
export default class Main extends Component {
|
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)
|
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({
|
this.setState({
|
||||||
json: setIn(this.state.json, path, value)
|
json: setIn(this.state.json, path, newObject)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.<string | number>} 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
|
|
|
@ -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]'
|
|
||||||
}
|
|
|
@ -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.<string | number>} 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.<string | number>} 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
|
||||||
|
}
|
||||||
|
|
|
@ -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.<string | number>} 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
|
|
Loading…
Reference in New Issue