Largely fixed jumping of fields when renaming. Some refactoring.

This commit is contained in:
jos 2016-07-13 14:26:11 +02:00
parent 8723855bdf
commit c0f075a9e6
8 changed files with 136 additions and 121 deletions

View File

@ -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;

View File

@ -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)
} }
} }

View File

@ -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)
}) })
} }

View File

@ -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
}
}

View File

@ -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

View File

@ -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]'
}

121
src/utils/objectUtils.js Normal file
View File

@ -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
}

View File

@ -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