Changed internal path to Array again

This commit is contained in:
jos 2016-07-30 20:30:08 +02:00
parent 667d3f32aa
commit 5416676735
2 changed files with 60 additions and 78 deletions

View File

@ -2,6 +2,7 @@ import { h, Component } from 'preact'
import ContextMenu from './ContextMenu' import ContextMenu from './ContextMenu'
import { escapeHTML, unescapeHTML } from './utils/stringUtils' import { escapeHTML, unescapeHTML } from './utils/stringUtils'
import { last } from './utils/arrayUtils'
import { getInnerText } from './utils/domUtils' import { getInnerText } from './utils/domUtils'
import {stringConvert, valueType, isUrl} from './utils/typeUtils' import {stringConvert, valueType, isUrl} from './utils/typeUtils'
@ -45,13 +46,13 @@ export default class JSONNode extends Component {
} }
} }
renderJSONObject ({data, path, index, options, events}) { renderJSONObject ({path, data, options, events}) {
const childCount = data.childs.length const childCount = data.childs.length
const contents = [ const contents = [
h('div', {class: 'jsoneditor-node jsoneditor-object'}, [ h('div', {class: 'jsoneditor-node jsoneditor-object'}, [
this.renderExpandButton(), this.renderExpandButton(),
this.renderContextMenuButton(), this.renderContextMenuButton(),
this.renderProperty(data, index, options), this.renderProperty(path, data, options),
this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact) this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact)
this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`) this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`)
]) ])
@ -60,7 +61,7 @@ export default class JSONNode extends Component {
if (data.expanded) { if (data.expanded) {
const childs = data.childs.map(child => { const childs = data.childs.map(child => {
return h(JSONNode, { return h(JSONNode, {
path: path + '/' + child.prop, path: path.concat(child.prop),
data: child, data: child,
options, options,
events events
@ -73,13 +74,13 @@ export default class JSONNode extends Component {
return h('li', {}, contents) return h('li', {}, contents)
} }
renderJSONArray ({data, path, index, options, events}) { renderJSONArray ({path, data, options, events}) {
const childCount = data.childs.length const childCount = data.childs.length
const contents = [ const contents = [
h('div', {class: 'jsoneditor-node jsoneditor-array'}, [ h('div', {class: 'jsoneditor-node jsoneditor-array'}, [
this.renderExpandButton(), this.renderExpandButton(),
this.renderContextMenuButton(), this.renderContextMenuButton(),
this.renderProperty(data, index, options), this.renderProperty(path, data, options),
this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact) this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact)
this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`) this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`)
]) ])
@ -88,9 +89,8 @@ export default class JSONNode extends Component {
if (data.expanded) { if (data.expanded) {
const childs = data.childs.map((child, index) => { const childs = data.childs.map((child, index) => {
return h(JSONNode, { return h(JSONNode, {
path: path + '/' + index, path: path.concat(index),
data: child, data: child,
index,
options, options,
events events
}) })
@ -102,12 +102,12 @@ export default class JSONNode extends Component {
return h('li', {}, contents) return h('li', {}, contents)
} }
renderJSONValue ({data, index, options}) { renderJSONValue ({path, data, options}) {
return h('li', {}, [ return h('li', {}, [
h('div', {class: 'jsoneditor-node'}, [ h('div', {class: 'jsoneditor-node'}, [
h('div', {class: 'jsoneditor-button-placeholder'}), h('div', {class: 'jsoneditor-button-placeholder'}),
this.renderContextMenuButton(), this.renderContextMenuButton(),
this.renderProperty(data, index, options), this.renderProperty(path, data, options),
this.renderSeparator(), this.renderSeparator(),
this.renderValue(data.value) this.renderValue(data.value)
]) ])
@ -118,20 +118,29 @@ export default class JSONNode extends Component {
return h('div', {class: 'jsoneditor-readonly', contentEditable: false, title}, text) return h('div', {class: 'jsoneditor-readonly', contentEditable: false, title}, text)
} }
renderProperty (data, index, options) { renderProperty (path, data, options) {
const isProperty = typeof data.prop === 'string' if (path.length > 0) {
const content = isProperty const content = last(path)
? escapeHTML(data.prop) // render the property name const isIndex = typeof content === 'number'
: index !== undefined
? index // render the array index of the item
: JSONNode._rootName(data, options)
return h('div', { return h('div', {
class: 'jsoneditor-property' + (isProperty ? '' : ' jsoneditor-readonly'), class: 'jsoneditor-property' + (isIndex ? ' jsoneditor-readonly' : ''),
contentEditable: isProperty, contentEditable: !isIndex,
spellCheck: 'false', spellCheck: 'false',
onInput: this.handleChangeProperty onInput: this.handleChangeProperty
}, content) }, content)
}
else {
// root node
const content = JSONNode._rootName(data, options)
return h('div', {
class: 'jsoneditor-property jsoneditor-readonly',
contentEditable: false,
spellCheck: 'false',
onInput: this.handleChangeProperty
}, content)
}
} }
renderSeparator() { renderSeparator() {
@ -175,7 +184,7 @@ export default class JSONNode extends Component {
renderContextMenu ({anchor, root}) { renderContextMenu ({anchor, root}) {
const path = this.props.path const path = this.props.path
const hasParent = path !== '' const hasParent = path.length > 0
const type = this.props.data.type const type = this.props.data.type
const events = this.props.events const events = this.props.events
const items = [] // array with menu items const items = [] // array with menu items
@ -321,10 +330,9 @@ export default class JSONNode extends Component {
const newProp = unescapeHTML(getInnerText(event.target)) const newProp = unescapeHTML(getInnerText(event.target))
// remove last entry from the path to get the path of the parent object // remove last entry from the path to get the path of the parent object
const index = this.props.path.lastIndexOf('/') const parentPath = this.props.path.slice(0, this.props.path.length - 1)
const path = this.props.path.substr(0, index)
this.props.events.onChangeProperty(path, oldProp, newProp) this.props.events.onChangeProperty(parentPath, oldProp, newProp)
} }
handleChangeValue (event) { handleChangeValue (event) {

View File

@ -2,7 +2,7 @@ import { h, Component } from 'preact'
import * as pointer from 'json-pointer' import * as pointer from 'json-pointer'
import { setIn, updateIn, getIn, deleteIn, cloneDeep } from './utils/objectUtils' import { setIn, updateIn, getIn, deleteIn, cloneDeep } from './utils/objectUtils'
import { compareAsc, compareDesc } from './utils/arrayUtils' import { compareAsc, compareDesc, last } from './utils/arrayUtils'
import { isObject } from './utils/typeUtils' import { isObject } from './utils/typeUtils'
import bindMethods from './utils/bindMethods' import bindMethods from './utils/bindMethods'
import JSONNode from './JSONNode' import JSONNode from './JSONNode'
@ -23,7 +23,6 @@ export default class Main extends Component {
data: { data: {
type: 'object', type: 'object',
expanded: true, expanded: true,
path: [],
childs: [] childs: []
}, },
@ -56,7 +55,7 @@ export default class Main extends Component {
data: this.state.data, data: this.state.data,
events: this.state.events, events: this.state.events,
options: this.state.options, options: this.state.options,
path: '' path: []
}) })
]) ])
]) ])
@ -72,7 +71,7 @@ export default class Main extends Component {
console.log('handleChangeProperty', path, oldProp, newProp) console.log('handleChangeProperty', path, oldProp, newProp)
const index = this._findIndex(path, oldProp) const index = this._findIndex(path, oldProp)
const newPath = path + '/' + pointer.escape(newProp) const newPath = path.concat(newProp)
this._setIn(path, ['childs', index, 'path'], newPath) this._setIn(path, ['childs', index, 'path'], newPath)
this._setIn(path, ['childs', index, 'prop'], newProp) this._setIn(path, ['childs', index, 'prop'], newProp)
@ -91,11 +90,8 @@ export default class Main extends Component {
// TODO: this method is quite complicated. Can we simplify it? // TODO: this method is quite complicated. Can we simplify it?
const parsedPath = pointer.parse(path) const afterProp = last(path)
const afterProp = parsedPath[parsedPath.length - 1] const parentPath = path.slice(0, path.length - 1)
const parentPath = parsedPath.slice(0, parsedPath.length - 1)
.map(entry => '/' + pointer.escape(entry)).join('')
const parent = this._getIn(parentPath) const parent = this._getIn(parentPath)
const index = parent.type === 'array' const index = parent.type === 'array'
@ -126,11 +122,8 @@ export default class Main extends Component {
// TODO: this method is quite complicated. Can we simplify it? // TODO: this method is quite complicated. Can we simplify it?
const parsedPath = pointer.parse(path) const prop = last(path)
const prop = parsedPath[parsedPath.length - 1] const parentPath = path.slice(0, path.length - 1)
const parentPath = parsedPath.slice(0, parsedPath.length - 1)
.map(entry => '/' + pointer.escape(entry)).join('')
const parent = this._getIn(parentPath) const parent = this._getIn(parentPath)
const index = parent.type === 'array' const index = parent.type === 'array'
@ -157,8 +150,8 @@ export default class Main extends Component {
} }
/** /**
* * Order the childs of an array in ascending or descending order
* @param path * @param {Array.<string | number>} path
* @param {'asc' | 'desc' | null} [order=null] If not provided, will toggle current ordering * @param {'asc' | 'desc' | null} [order=null] If not provided, will toggle current ordering
*/ */
handleSort (path, order = null) { handleSort (path, order = null) {
@ -166,11 +159,6 @@ export default class Main extends Component {
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
const comparators = {
asc: compareAsc,
desc: compareDesc
}
let _order let _order
if (order === 'asc' || order === 'desc') { if (order === 'asc' || order === 'desc') {
_order = order _order = order
@ -184,7 +172,7 @@ export default class Main extends Component {
this._updateIn(path, ['childs'], function (childs) { this._updateIn(path, ['childs'], function (childs) {
const ordered = childs.slice(0) const ordered = childs.slice(0)
const compare = comparators[_order] || comparators['asc'] const compare = _order === 'desc' ? compareDesc : compareAsc
ordered.sort((a, b) => compare(a.value, b.value)) ordered.sort((a, b) => compare(a.value, b.value))
@ -199,14 +187,14 @@ export default class Main extends Component {
} }
/** /**
* Set ContextMenu to a json pointer, or hide the context menu by passing null * Set ContextMenu to a json pointer, or hide the context menu by passing null as path
* @param {string | null} path * @param {Array.<string | number> | null} path
* @param {Element} anchor * @param {Element} anchor
* @param {Element} root * @param {Element} root
* @private * @private
*/ */
handleShowContextMenu({path, anchor, root}) { handleShowContextMenu({path, anchor, root}) {
let data = this.state.data console.log('handleShowContextMenu', path, anchor, root)
// TODO: remove this cached this.state.contextMenuPath and do a brute-force sweep over the data instead? // TODO: remove this cached this.state.contextMenuPath and do a brute-force sweep over the data instead?
// hide previous context menu (if any) // hide previous context menu (if any)
@ -215,12 +203,12 @@ export default class Main extends Component {
} }
// show new menu // show new menu
if (typeof path === 'string') { if (Array.isArray(path)) {
this._setIn(path, ['contextMenu'], {anchor, root}) this._setIn(path, ['contextMenu'], {anchor, root})
} }
this.setState({ this.setState({
contextMenuPath: typeof path === 'string' ? path : null // store path of current menu, just to easily find it next time contextMenuPath: Array.isArray(path) ? path : null // store path of current menu, just to easily find it next time
}) })
} }
@ -230,13 +218,13 @@ export default class Main extends Component {
} }
_getIn (path, modelProps = []) { _getIn (path, modelProps = []) {
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)) const modelPath = Main._pathToModelPath(this.state.data, path)
return getIn(this.state.data, modelPath.concat(modelProps)) return getIn(this.state.data, modelPath.concat(modelProps))
} }
_setIn (path, modelProps = [], value) { _setIn (path, modelProps = [], value) {
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)) const modelPath = Main._pathToModelPath(this.state.data, path)
this.setState({ this.setState({
data: setIn(this.state.data, modelPath.concat(modelProps), value) data: setIn(this.state.data, modelPath.concat(modelProps), value)
@ -244,7 +232,7 @@ export default class Main extends Component {
} }
_updateIn (path, modelProps = [], callback) { _updateIn (path, modelProps = [], callback) {
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)) const modelPath = Main._pathToModelPath(this.state.data, path)
this.setState({ this.setState({
data: updateIn(this.state.data, modelPath.concat(modelProps), callback) data: updateIn(this.state.data, modelPath.concat(modelProps), callback)
@ -252,7 +240,7 @@ export default class Main extends Component {
} }
_deleteIn (path, modelProps = []) { _deleteIn (path, modelProps = []) {
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)) const modelPath = Main._pathToModelPath(this.state.data, path)
this.setState({ this.setState({
data: deleteIn(this.state.data, modelPath.concat(modelProps)) data: deleteIn(this.state.data, modelPath.concat(modelProps))
@ -272,7 +260,7 @@ export default class Main extends Component {
// TODO: comment // TODO: comment
set (json) { set (json) {
this.setState({ this.setState({
data: Main._jsonToModel('', null, json, this.state.options.expand) data: Main._jsonToModel([], null, json, this.state.options.expand)
}) })
} }
@ -281,25 +269,11 @@ export default class Main extends Component {
* *
* Rule: expand the root node only * Rule: expand the root node only
* *
* @param {string} path A JSON Pointer path * @param {Array.<string | number>} path
* @return {boolean} * @return {boolean}
*/ */
static expand (path) { static expand (path) {
return path.indexOf('/') === -1 return path.length === 0
}
/**
* 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
})
} }
/** /**
@ -330,10 +304,10 @@ 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 {string} path * @param {Array.<string | number>} path
* @param {string | null} prop * @param {string | null} prop
* @param {Object | Array | string | number | boolean | null} value * @param {Object | Array | string | number | boolean | null} value
* @param {function(path: string)} expand * @param {function(path: Array.<string | number>)} expand
* @return {Model} * @return {Model}
* @private * @private
*/ */
@ -343,7 +317,7 @@ export default class Main extends Component {
type: 'array', type: 'array',
expanded: expand(path), expanded: expand(path),
prop, prop,
childs: value.map((child, index) => Main._jsonToModel(path + '/' + index, null, child, expand)) childs: value.map((child, index) => Main._jsonToModel(path.concat(index), null, child, expand))
} }
} }
else if (isObject(value)) { else if (isObject(value)) {
@ -352,7 +326,7 @@ export default class Main extends Component {
expanded: expand(path), expanded: expand(path),
prop, prop,
childs: Object.keys(value).map(prop => { childs: Object.keys(value).map(prop => {
return Main._jsonToModel(path + '/' + pointer.escape(prop), prop, value[prop], expand) return Main._jsonToModel(path.concat(prop), prop, value[prop], expand)
}) })
} }
} }