Refactored path to be a JSON pointer

This commit is contained in:
jos 2016-07-16 14:24:28 +02:00
parent 37ae2111ee
commit cd08e16789
3 changed files with 60 additions and 31 deletions

View File

@ -4,7 +4,7 @@ import ContextMenu from './ContextMenu'
import { escapeHTML, unescapeHTML } from './utils/stringUtils' import { escapeHTML, unescapeHTML } from './utils/stringUtils'
import { getInnerText } from './utils/domUtils' import { getInnerText } from './utils/domUtils'
import {stringConvert, valueType, isUrl} from './utils/typeUtils' import {stringConvert, valueType, isUrl} from './utils/typeUtils'
import { last } from './utils/arrayUtils' import * as pointer from 'json-pointer'
export default class JSONNode extends Component { export default class JSONNode extends Component {
constructor (props) { constructor (props) {
@ -102,10 +102,9 @@ export default class JSONNode extends Component {
} }
renderProperty (data, index, options) { renderProperty (data, index, options) {
const property = last(data.path) const isProperty = typeof data.prop === 'string'
const isProperty = typeof property === 'string'
const content = isProperty const content = isProperty
? escapeHTML(property) // render the property name ? escapeHTML(data.prop) // render the property name
: index !== undefined : index !== undefined
? index // render the array index of the item ? index // render the array index of the item
: JSONNode._rootName(data, options) : JSONNode._rootName(data, options)
@ -169,11 +168,11 @@ export default class JSONNode extends Component {
} }
handleChangeProperty (event) { handleChangeProperty (event) {
const property = unescapeHTML(getInnerText(event.target)) const oldProp = this.props.data.prop
const oldPath = this.props.data.path const newProp = unescapeHTML(getInnerText(event.target))
const newPath = oldPath.slice(0, oldPath.length - 1).concat(property) const path = this.props.data.path.replace(/\/.+$/, '') // remove last entry
this.props.events.onChangeProperty(oldPath, newPath) this.props.events.onChangeProperty(path, oldProp, newProp)
} }
handleChangeValue (event) { handleChangeValue (event) {

View File

@ -1,7 +1,7 @@
import { h, Component } from 'preact' import { h, Component } from 'preact'
import * as pointer from 'json-pointer'
import { setIn } from './utils/objectUtils' import { setIn, getIn } from './utils/objectUtils'
import { last } from './utils/arrayUtils'
import { isObject } from './utils/typeUtils' import { isObject } from './utils/typeUtils'
import JSONNode from './JSONNode' import JSONNode from './JSONNode'
@ -46,25 +46,34 @@ export default class Main extends Component {
_onChangeValue (path, value) { _onChangeValue (path, value) {
console.log('_onChangeValue', path, value) console.log('_onChangeValue', path, value)
const modelPath = Main._pathToModelPath(this.state.data, path).concat('value') const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)).concat('value')
this.setState({ this.setState({
data: setIn(this.state.data, modelPath, value) data: setIn(this.state.data, modelPath, value)
}) })
} }
_onChangeProperty (oldPath, newPath) { _onChangeProperty (path, oldProp, newProp) {
console.log('_onChangeProperty', oldPath, newPath) console.log('_onChangeProperty', path, oldProp, newProp)
const modelPath = Main._pathToModelPath(this.state.data, oldPath).concat('path') const array = pointer.parse(path)
const parent = getIn(this.state.data, array)
const index = parent.childs.findIndex(child => child.prop === oldProp)
this.setState({ const newPath = path + '/' + pointer.escape(newProp)
data: setIn(this.state.data, modelPath, newPath) const modelPath = Main._pathToModelPath(this.state.data, array).concat(['childs', index])
})
let data = this.state.data
data = setIn(data, modelPath.concat('path'), newPath)
data = setIn(data, modelPath.concat('prop'), newProp)
this.setState({ data })
} }
_onExpand(path, expand) { _onExpand(path, expand) {
const modelPath = Main._pathToModelPath(this.state.data, path).concat('expanded') const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)).concat('expanded')
console.log('_onExpand', path, modelPath)
this.setState({ this.setState({
data: setIn(this.state.data, modelPath, expand) data: setIn(this.state.data, modelPath, expand)
@ -72,7 +81,7 @@ export default class Main extends Component {
} }
_onContextMenu(path, visible) { _onContextMenu(path, visible) {
const modelPath = Main._pathToModelPath(this.state.data, path).concat('menu') const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)).concat('menu')
this.setState({ this.setState({
data: setIn(this.state.data, modelPath, visible) data: setIn(this.state.data, modelPath, visible)
@ -87,17 +96,34 @@ export default class Main extends Component {
// TODO: comment // TODO: comment
set (json) { set (json) {
this.setState({ this.setState({
data: Main._jsonToModel([], json, this.state.options.expand) data: Main._jsonToModel('', null, json, this.state.options.expand)
}) })
} }
/** /**
* Default function to determine whether or not to expand a node initially * Default function to determine whether or not to expand a node initially
* @param {Array.<string | number>} path *
* Rule: expand the root node only
*
* @param {string} path A JSON Pointer path
* @return {boolean} * @return {boolean}
*/ */
static expand (path) { static expand (path) {
return path.length === 0 return path.indexOf('/') === -1
}
/**
* parse json pointer into an array, and replace strings containing a number
* with a number
* @param {string} path
* @return {Array.<string | number>}
* @private
*/
static _parsePath (path) {
return pointer.parse(path).map(item => {
const num = Number(item)
return isNaN(num) ? item : num
})
} }
/** /**
@ -119,7 +145,7 @@ export default class Main extends Component {
} }
else { else {
// object property. find the index of this property // object property. find the index of this property
index = model.childs.findIndex(child => last(child.path) === path[0]) index = model.childs.findIndex(child => child.prop === path[0])
} }
return ['childs', index] return ['childs', index]
@ -128,19 +154,21 @@ export default class Main extends Component {
/** /**
* Convert a JSON object into the internally used data model * Convert a JSON object into the internally used data model
* @param {Array.<string | number>} path * @param {string} path
* @param {string | null} prop
* @param {Object | Array | string | number | boolean | null} value * @param {Object | Array | string | number | boolean | null} value
* @param {function(path: Array.<string>)} expand * @param {function(path: string)} expand
* @return {Model} * @return {Model}
* @private * @private
*/ */
static _jsonToModel (path, value, expand) { static _jsonToModel (path, prop, value, expand) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return { return {
type: 'array', type: 'array',
expanded: expand(path), expanded: expand(path),
path, path,
childs: value.map((child, index) => Main._jsonToModel(path.concat(index), child, expand)) prop,
childs: value.map((child, index) => Main._jsonToModel(path + '/' + index, null, child, expand))
} }
} }
else if (isObject(value)) { else if (isObject(value)) {
@ -148,8 +176,9 @@ export default class Main extends Component {
type: 'object', type: 'object',
expanded: expand(path), expanded: expand(path),
path, path,
prop,
childs: Object.keys(value).map(prop => { childs: Object.keys(value).map(prop => {
return Main._jsonToModel(path.concat(prop), value[prop], expand) return Main._jsonToModel(path + '/' + pointer.escape(prop), prop, value[prop], expand)
}) })
} }
} }
@ -157,6 +186,7 @@ export default class Main extends Component {
return { return {
type: 'auto', type: 'auto',
path, path,
prop,
value value
} }
} }
@ -176,8 +206,7 @@ export default class Main extends Component {
const object = {} const object = {}
model.childs.forEach(child => { model.childs.forEach(child => {
const prop = last(child.path) object[child.prop] = Main._modelToJson(child)
object[prop] = Main._modelToJson(child)
}) })
return object return object

View File

@ -4,7 +4,8 @@
* type: string, * type: string,
* expanded: boolean?, * expanded: boolean?,
* menu: boolean?, * menu: boolean?,
* path: Array.<string | number>, * path: string,
* prop: string?,
* value: *?, * value: *?,
* childs: Model[]? * childs: Model[]?
* }} Model * }} Model