Finished switch to React

This commit is contained in:
jos 2016-12-04 22:14:21 +01:00
parent 4208dff7b9
commit 98f56efc47
16 changed files with 148 additions and 120 deletions

View File

@ -1,4 +1,4 @@
import { h, Component } from 'preact' import { createElement as h, Component } from 'react'
import ace from '../assets/ace' import ace from '../assets/ace'
/** /**
@ -19,7 +19,7 @@ export default class Ace extends Component {
} }
render (props, state) { render (props, state) {
return h('div', {id: this.id, class: 'jsoneditor-code'}) return h('div', {ref: 'container', className: 'jsoneditor-code'})
} }
shouldComponentUpdate () { shouldComponentUpdate () {
@ -28,7 +28,7 @@ export default class Ace extends Component {
} }
componentDidMount () { componentDidMount () {
const container = this.base const container = this.refs.container
// use ace from bundle, and if not available // use ace from bundle, and if not available
// try to use from options or else from global // try to use from options or else from global

View File

@ -37,11 +37,10 @@ export default class CodeMode extends TextMode {
} }
render () { render () {
const { props, state } = this
return h('div', {className: 'jsoneditor jsoneditor-mode-code'}, [ return h('div', {className: 'jsoneditor jsoneditor-mode-code'}, [
this.renderMenu(), this.renderMenu(),
h('div', {className: 'jsoneditor-contents'}, h(Ace, { h('div', {key: 'contents', className: 'jsoneditor-contents'}, h(Ace, {
value: this.state.text, value: this.state.text,
onChange: this.handleChange, onChange: this.handleChange,
onLoadAce: this.props.options.onLoadAce, onLoadAce: this.props.options.onLoadAce,

View File

@ -41,7 +41,7 @@ export default class JSONNode extends Component {
renderJSONObject ({prop, data, options, events}) { renderJSONObject ({prop, data, options, events}) {
const childCount = data.props.length const childCount = data.props.length
const contents = [ const contents = [
h('div', {className: 'jsoneditor-node jsoneditor-object'}, [ h('div', {key: 'node', className: 'jsoneditor-node jsoneditor-object'}, [
this.renderExpandButton(), this.renderExpandButton(),
this.renderActionMenuButton(), this.renderActionMenuButton(),
this.renderProperty(prop, data, options), this.renderProperty(prop, data, options),
@ -66,9 +66,9 @@ export default class JSONNode extends Component {
contents.push(h('ul', {key: 'props', className: 'jsoneditor-list'}, props)) contents.push(h('ul', {key: 'props', className: 'jsoneditor-list'}, props))
} }
else { else {
contents.push(h('ul', {key: 'append', className: 'jsoneditor-list'}, [ contents.push(h('ul', {key: 'append', className: 'jsoneditor-list'},
this.renderAppend('(empty object)') this.renderAppend('(empty object)')
])) ))
} }
} }
@ -78,7 +78,7 @@ export default class JSONNode extends Component {
renderJSONArray ({prop, data, options, events}) { renderJSONArray ({prop, data, options, events}) {
const childCount = data.items.length const childCount = data.items.length
const contents = [ const contents = [
h('div', {className: 'jsoneditor-node jsoneditor-array'}, [ h('div', {key: 'node', className: 'jsoneditor-node jsoneditor-array'}, [
this.renderExpandButton(), this.renderExpandButton(),
this.renderActionMenuButton(), this.renderActionMenuButton(),
this.renderProperty(prop, data, options), this.renderProperty(prop, data, options),
@ -102,9 +102,9 @@ export default class JSONNode extends Component {
contents.push(h('ul', {key: 'items', className: 'jsoneditor-list'}, items)) contents.push(h('ul', {key: 'items', className: 'jsoneditor-list'}, items))
} }
else { else {
contents.push(h('ul', {key: 'append', className: 'jsoneditor-list'}, [ contents.push(h('ul', {key: 'append', className: 'jsoneditor-list'},
this.renderAppend('(empty array)') this.renderAppend('(empty array)')
])) ))
} }
} }
@ -112,7 +112,7 @@ export default class JSONNode extends Component {
} }
renderJSONValue ({prop, data, options}) { renderJSONValue ({prop, data, options}) {
return h('li', {}, [ return h('li', {},
h('div', {className: 'jsoneditor-node'}, [ h('div', {className: 'jsoneditor-node'}, [
this.renderPlaceholder(), this.renderPlaceholder(),
this.renderActionMenuButton(), this.renderActionMenuButton(),
@ -121,7 +121,7 @@ export default class JSONNode extends Component {
this.renderValue(data.value, data.searchValue, options), this.renderValue(data.value, data.searchValue, options),
this.renderError(data.error) this.renderError(data.error)
]) ])
]) )
} }
/** /**
@ -130,21 +130,21 @@ export default class JSONNode extends Component {
* @return {*} * @return {*}
*/ */
renderAppend (text) { renderAppend (text) {
return h('li', {key: 'append'}, [ return h('li', {key: 'append'},
h('div', {className: 'jsoneditor-node'}, [ h('div', {className: 'jsoneditor-node'}, [
this.renderPlaceholder(), this.renderPlaceholder(),
this.renderAppendMenuButton(), this.renderAppendMenuButton(),
this.renderReadonly(text) this.renderReadonly(text)
]) ])
]) )
} }
renderPlaceholder () { renderPlaceholder () {
return h('div', {className: 'jsoneditor-button-placeholder'}) return h('div', {key: 'placeholder', className: 'jsoneditor-button-placeholder'})
} }
renderReadonly (text, title = null) { renderReadonly (text, title = null) {
return h('div', {className: 'jsoneditor-readonly', title}, text) return h('div', {key: 'readonly', className: 'jsoneditor-readonly', title}, text)
} }
renderProperty (prop, data, options) { renderProperty (prop, data, options) {
@ -153,6 +153,7 @@ export default class JSONNode extends Component {
const rootName = JSONNode.getRootName(data, options) const rootName = JSONNode.getRootName(data, options)
return h('div', { return h('div', {
key: 'property',
ref: 'property', ref: 'property',
className: 'jsoneditor-property jsoneditor-readonly', className: 'jsoneditor-property jsoneditor-readonly',
spellCheck: 'false', spellCheck: 'false',
@ -170,6 +171,7 @@ export default class JSONNode extends Component {
const escapedProp = escapeHTML(prop, options.escapeUnicode) const escapedProp = escapeHTML(prop, options.escapeUnicode)
return h('div', { return h('div', {
key: 'property',
className: 'jsoneditor-property' + emptyClassName + searchClassName, className: 'jsoneditor-property' + emptyClassName + searchClassName,
contentEditable: 'true', contentEditable: 'true',
spellCheck: 'false', spellCheck: 'false',
@ -178,6 +180,7 @@ export default class JSONNode extends Component {
} }
else { else {
return h('div', { return h('div', {
key: 'property',
className: 'jsoneditor-property jsoneditor-readonly' + searchClassName, className: 'jsoneditor-property jsoneditor-readonly' + searchClassName,
spellCheck: 'false' spellCheck: 'false'
}, prop) }, prop)
@ -185,7 +188,7 @@ export default class JSONNode extends Component {
} }
renderSeparator() { renderSeparator() {
return h('div', {className: 'jsoneditor-separator'}, ':') return h('div', {key: 'separator', className: 'jsoneditor-separator'}, ':')
} }
renderValue (value, searchValue, options) { renderValue (value, searchValue, options) {
@ -197,6 +200,7 @@ export default class JSONNode extends Component {
const editable = !options.isValueEditable || options.isValueEditable(this.getPath()) const editable = !options.isValueEditable || options.isValueEditable(this.getPath())
if (editable) { if (editable) {
return h('div', { return h('div', {
key: 'value',
ref: 'value', ref: 'value',
className: JSONNode.getValueClass(type, itsAnUrl, isEmpty, searchValue), className: JSONNode.getValueClass(type, itsAnUrl, isEmpty, searchValue),
contentEditable: 'true', contentEditable: 'true',
@ -210,6 +214,7 @@ export default class JSONNode extends Component {
} }
else { else {
return h('div', { return h('div', {
key: 'value',
className: 'jsoneditor-readonly', className: 'jsoneditor-readonly',
title: itsAnUrl ? JSONNode.URL_TITLE : null title: itsAnUrl ? JSONNode.URL_TITLE : null
}, escapedValue) }, escapedValue)
@ -219,12 +224,14 @@ export default class JSONNode extends Component {
renderError (error) { renderError (error) {
if (error) { if (error) {
return h('button', { return h('button', {
type: 'button', key: 'error',
class: 'jsoneditor-schema-error', ref: 'error',
onFocus: this.updatePopoverDirection, type: 'button',
onMouseOver: this.updatePopoverDirection className: 'jsoneditor-schema-error',
}, onFocus: this.updatePopoverDirection,
h('div', {class: 'jsoneditor-popover jsoneditor-right'}, error.message) onMouseOver: this.updatePopoverDirection
},
h('div', {className: 'jsoneditor-popover jsoneditor-right'}, error.message)
) )
} }
else { else {
@ -247,7 +254,8 @@ export default class JSONNode extends Component {
popover.className = 'jsoneditor-popover jsoneditor-' + direction popover.className = 'jsoneditor-popover jsoneditor-' + direction
// FIXME: the contentRect is that of the whole contents, not the visible window // FIXME: the contentRect is that of the whole contents, not the visible window
const contents = this.base.parentNode.parentNode // TODO: use a ref on the root of the node instead of this parentNode chain?
const contents = this.refs.error.parentNode.parentNode.parentNode
const contentRect = contents.getBoundingClientRect() const contentRect = contents.getBoundingClientRect()
const popoverRect = popover.getBoundingClientRect() const popoverRect = popover.getBoundingClientRect()
const margin = 20 // account for a scroll bar const margin = 20 // account for a scroll bar
@ -318,7 +326,8 @@ export default class JSONNode extends Component {
renderExpandButton () { renderExpandButton () {
const className = `jsoneditor-button jsoneditor-${this.props.data.expanded ? 'expanded' : 'collapsed'}` const className = `jsoneditor-button jsoneditor-${this.props.data.expanded ? 'expanded' : 'collapsed'}`
return h('div', {className: 'jsoneditor-button-container'},
return h('div', {key: 'expand', className: 'jsoneditor-button-container'},
h('button', { h('button', {
className: className, className: className,
onClick: this.handleExpand, onClick: this.handleExpand,
@ -331,6 +340,7 @@ export default class JSONNode extends Component {
renderActionMenuButton () { renderActionMenuButton () {
return h(ActionButton, { return h(ActionButton, {
key: 'action',
path: this.getPath(), path: this.getPath(),
type: this.props.data.type, type: this.props.data.type,
events: this.props.events events: this.props.events
@ -339,6 +349,7 @@ export default class JSONNode extends Component {
renderAppendMenuButton () { renderAppendMenuButton () {
return h(AppendActionButton, { return h(AppendActionButton, {
key: 'append',
path: this.getPath(), path: this.getPath(),
events: this.props.events events: this.props.events
}) })

View File

@ -26,6 +26,7 @@ export default class JSONNodeForm extends JSONNode {
if (isIndex) { // array item if (isIndex) { // array item
return h('div', { return h('div', {
key: 'property',
className: 'jsoneditor-property jsoneditor-readonly' className: 'jsoneditor-property jsoneditor-readonly'
}, prop) }, prop)
} }
@ -33,6 +34,7 @@ export default class JSONNodeForm extends JSONNode {
const escapedProp = escapeHTML(prop, options.escapeUnicode) const escapedProp = escapeHTML(prop, options.escapeUnicode)
return h('div', { return h('div', {
key: 'property',
className: 'jsoneditor-property' + (prop.length === 0 ? ' jsoneditor-empty' : '') className: 'jsoneditor-property' + (prop.length === 0 ? ' jsoneditor-empty' : '')
}, escapedProp) }, escapedProp)
} }
@ -42,6 +44,7 @@ export default class JSONNodeForm extends JSONNode {
const content = JSONNode.getRootName(data, options) const content = JSONNode.getRootName(data, options)
return h('div', { return h('div', {
key: 'property',
className: 'jsoneditor-property jsoneditor-readonly' className: 'jsoneditor-property jsoneditor-readonly'
}, content) }, content)
} }

View File

@ -22,12 +22,14 @@ export default class JSONNodeView extends JSONNodeForm {
if (itsAnUrl) { if (itsAnUrl) {
return h('a', { return h('a', {
key: 'value',
className: className, className: className,
href: escapedValue href: escapedValue
}, escapedValue) }, escapedValue)
} }
else { else {
return h('div', { return h('div', {
key: 'value',
className: className, className: className,
onClick: this.handleClickValue onClick: this.handleClickValue
}, escapedValue) }, escapedValue)

View File

@ -48,18 +48,17 @@ export default class TextMode extends Component {
} }
render () { render () {
const { props, state} = this
return h('div', {className: 'jsoneditor jsoneditor-mode-text'}, [ return h('div', {className: 'jsoneditor jsoneditor-mode-text'}, [
this.renderMenu(), this.renderMenu(),
h('div', {className: 'jsoneditor-contents'}, [ h('div', {key: 'contents', className: 'jsoneditor-contents'},
h('textarea', { h('textarea', {
className: 'jsoneditor-text', className: 'jsoneditor-text',
value: this.state.text, value: this.state.text,
onInput: this.handleChange onChange: this.handleChange,
onInput: this.handleInput
}) })
]), ),
this.renderSchemaErrors () this.renderSchemaErrors ()
]) ])
@ -68,13 +67,15 @@ export default class TextMode extends Component {
/** @protected */ /** @protected */
renderMenu () { renderMenu () {
// TODO: move Menu into a separate Component // TODO: move Menu into a separate Component
return h('div', {className: 'jsoneditor-menu'}, [ return h('div', {key: 'menu', className: 'jsoneditor-menu'}, [
h('button', { h('button', {
key: 'format',
className: 'jsoneditor-format', className: 'jsoneditor-format',
title: 'Format the JSON document', title: 'Format the JSON document',
onClick: this.handleFormat onClick: this.handleFormat
}), }),
h('button', { h('button', {
key: 'compact',
className: 'jsoneditor-compact', className: 'jsoneditor-compact',
title: 'Compact the JSON document', title: 'Compact the JSON document',
onClick: this.handleCompact onClick: this.handleCompact
@ -82,9 +83,13 @@ export default class TextMode extends Component {
// TODO: implement a button "Repair" // TODO: implement a button "Repair"
h('div', {className: 'jsoneditor-vertical-menu-separator'}), h('div', {
key: 'separator',
className: 'jsoneditor-vertical-menu-separator'
}),
this.props.options.modes && h(ModeButton, { this.props.options.modes && h(ModeButton, {
key: 'mode',
modes: this.props.options.modes, modes: this.props.options.modes,
mode: this.props.mode, mode: this.props.mode,
onChangeMode: this.props.onChangeMode, onChangeMode: this.props.onChangeMode,
@ -110,7 +115,7 @@ export default class TextMode extends Component {
console.log('errors', allErrors) console.log('errors', allErrors)
return h('div', { class: 'jsoneditor-errors'}, return h('div', { className: 'jsoneditor-errors'},
h('table', {}, h('table', {},
h('tbody', {}, limitedErrors.map(TextMode.renderSchemaError)) h('tbody', {}, limitedErrors.map(TextMode.renderSchemaError))
) )
@ -124,7 +129,7 @@ export default class TextMode extends Component {
// no valid JSON // no valid JSON
// TODO: display errors in text mode somehow? shouldn't be too much in your face // TODO: display errors in text mode somehow? shouldn't be too much in your face
// maybe a warning icon top right? // maybe a warning icon top right?
// return h('table', {class: 'jsoneditor-text-errors'}, // return h('table', {className: 'jsoneditor-text-errors'},
// h('tbody', {}, TextMode.renderSchemaError(err)) // h('tbody', {}, TextMode.renderSchemaError(err))
// ) // )
return null return null
@ -137,22 +142,22 @@ export default class TextMode extends Component {
* @return {JSX.Element} * @return {JSX.Element}
*/ */
static renderSchemaError (error) { static renderSchemaError (error) {
const icon = h('input', {type: 'button', class: 'jsoneditor-schema-error'}) const icon = h('input', {type: 'button', className: 'jsoneditor-schema-error'})
if (error && error.schema && error.schemaPath) { if (error && error.schema && error.schemaPath) {
// this is an ajv error message // this is an ajv error message
return h('tr', {}, [ return h('tr', {}, [
h('td', {}, icon), h('td', {key: 'icon'}, icon),
h('td', {}, error.dataPath), h('td', {key: 'path'}, error.dataPath),
h('td', {}, error.message) h('td', {key: 'message'}, error.message)
]) ])
} }
else { else {
// any other error message // any other error message
console.log('error???', error) console.log('error???', error)
return h('tr', {}, return h('tr', {},
h('td', {}, icon), h('td', {key: 'icon'}, icon),
h('td', {colSpan: 2}, h('code', {}, String(error))) h('td', {key: 'message', colSpan: 2}, h('code', {}, String(error)))
) )
} }
} }
@ -191,12 +196,16 @@ export default class TextMode extends Component {
return this.props.options && this.props.options.indentation || 2 return this.props.options && this.props.options.indentation || 2
} }
handleChange = (event) => {
// do nothing...
}
/** /**
* handle changed text input in the textarea * handle changed text input in the textarea
* @param {Event} event * @param {Event} event
* @protected * @protected
*/ */
handleChange = (event) => { handleInput = (event) => {
this.setText(event.target.value) this.setText(event.target.value)
if (this.props.options && this.props.options.onChangeText) { if (this.props.options && this.props.options.onChangeText) {
@ -229,8 +238,8 @@ export default class TextMode extends Component {
* Format the json * Format the json
*/ */
format () { format () {
var json = this.get() const json = this.get()
var text = JSON.stringify(json, null, this.getIndentation()) const text = JSON.stringify(json, null, this.getIndentation())
this.setText(text) this.setText(text)
} }
@ -238,8 +247,8 @@ export default class TextMode extends Component {
* Compact the json * Compact the json
*/ */
compact () { compact () {
var json = this.get() const json = this.get()
var text = JSON.stringify(json) const text = JSON.stringify(json)
this.setText(text) this.setText(text)
} }

View File

@ -81,9 +81,7 @@ export default class TreeMode extends Component {
data = addSearchResults(data, searchResults) data = addSearchResults(data, searchResults)
data = addFocus(data, searchResults[0]) // TODO: change to using focus from state data = addFocus(data, searchResults[0]) // TODO: change to using focus from state
} }
console.log('render', data)
// TODO: pass number of search results to search box in top menu // TODO: pass number of search results to search box in top menu
return h('div', { return h('div', {
@ -92,7 +90,7 @@ export default class TreeMode extends Component {
}, [ }, [
this.renderMenu(), this.renderMenu(),
h('div', {className: 'jsoneditor-contents jsoneditor-tree-contents', onClick: this.handleHideMenus}, h('div', {key: 'contents', className: 'jsoneditor-contents jsoneditor-tree-contents', onClick: this.handleHideMenus},
h('ul', {className: 'jsoneditor-list jsoneditor-root'}, h('ul', {className: 'jsoneditor-list jsoneditor-root'},
h(Node, { h(Node, {
data, data,
@ -109,11 +107,13 @@ export default class TreeMode extends Component {
renderMenu () { renderMenu () {
let items = [ let items = [
h('button', { h('button', {
key: 'expand-all',
className: 'jsoneditor-expand-all', className: 'jsoneditor-expand-all',
title: 'Expand all objects and arrays', title: 'Expand all objects and arrays',
onClick: this.handleExpandAll onClick: this.handleExpandAll
}), }),
h('button', { h('button', {
key: 'collapse-all',
className: 'jsoneditor-collapse-all', className: 'jsoneditor-collapse-all',
title: 'Collapse all objects and arrays', title: 'Collapse all objects and arrays',
onClick: this.handleCollapseAll onClick: this.handleCollapseAll
@ -122,17 +122,17 @@ export default class TreeMode extends Component {
if (this.props.mode !== 'view' && this.props.options.history != false) { if (this.props.mode !== 'view' && this.props.options.history != false) {
items = items.concat([ items = items.concat([
h('div', {className: 'jsoneditor-vertical-menu-separator'}), h('div', {key: 'history-separator', className: 'jsoneditor-vertical-menu-separator'}),
h('div', {style: {display: 'inline-block'}}, [
h('button', {
className: 'jsoneditor-undo',
title: 'Undo last action',
disabled: !this.canUndo(),
onClick: this.undo
}),
]),
h('button', { h('button', {
key: 'undo',
className: 'jsoneditor-undo',
title: 'Undo last action',
disabled: !this.canUndo(),
onClick: this.undo
}),
h('button', {
key: 'redo',
className: 'jsoneditor-redo', className: 'jsoneditor-redo',
title: 'Redo', title: 'Redo',
disabled: !this.canRedo(), disabled: !this.canRedo(),
@ -143,9 +143,10 @@ export default class TreeMode extends Component {
if (this.props.options.modes ) { if (this.props.options.modes ) {
items = items.concat([ items = items.concat([
h('div', {className: 'jsoneditor-vertical-menu-separator'}), h('div', {key: 'mode-separator', className: 'jsoneditor-vertical-menu-separator'}),
h(ModeButton, { h(ModeButton, {
key: 'mode',
modes: this.props.options.modes, modes: this.props.options.modes,
mode: this.props.mode, mode: this.props.mode,
onChangeMode: this.props.onChangeMode, onChangeMode: this.props.onChangeMode,
@ -157,7 +158,7 @@ export default class TreeMode extends Component {
if (this.props.options.search !== false) { if (this.props.options.search !== false) {
// option search is true or undefined // option search is true or undefined
items = items.concat([ items = items.concat([
h('div', {class: 'jsoneditor-menu-panel-right'}, h('div', {key: 'search', className: 'jsoneditor-menu-panel-right'},
h(Search, { h(Search, {
text: this.state.search.text, text: this.state.search.text,
onChange: this.handleSearch, onChange: this.handleSearch,
@ -167,7 +168,7 @@ export default class TreeMode extends Component {
]) ])
} }
return h('div', {className: 'jsoneditor-menu'}, items) return h('div', {key: 'menu', className: 'jsoneditor-menu'}, items)
} }
/** /**

View File

@ -26,11 +26,16 @@ export default class ActionButton extends Component {
return h('div', {className: 'jsoneditor-button-container'}, [ return h('div', {className: 'jsoneditor-button-container'}, [
h(ActionMenu, { h(ActionMenu, {
key: 'menu',
...props, // path, type, events ...props, // path, type, events
...state, // open, anchor, root ...state, // open, anchor, root
onRequestClose: this.handleRequestClose onRequestClose: this.handleRequestClose
}), }),
h('button', {className: className, onClick: this.handleOpen}) h('button', {
key: 'button',
className,
onClick: this.handleOpen
})
]) ])
} }

View File

@ -26,11 +26,16 @@ export default class AppendActionButton extends Component {
return h('div', {className: 'jsoneditor-button-container'}, [ return h('div', {className: 'jsoneditor-button-container'}, [
h(AppendActionMenu, { h(AppendActionMenu, {
key: 'menu',
...props, // path, events ...props, // path, events
...state, // open, anchor, root ...state, // open, anchor, root
onRequestClose: this.handleRequestClose onRequestClose: this.handleRequestClose
}), }),
h('button', {className: className, onClick: this.handleOpen}) h('button', {
key: 'button',
className: className,
onClick: this.handleOpen
})
]) ])
} }

View File

@ -12,7 +12,7 @@ export default class AppendActionMenu extends Component {
const { props, state} = this const { props, state} = this
const items = [ const items = [
createAppend(props.path, props.events.onAppend) createAppend(props.path, props.events.onAppend)
] ]
// TODO: implement a hook to adjust the action menu // TODO: implement a hook to adjust the action menu

View File

@ -20,9 +20,7 @@ export default class Menu extends Component {
* @return {*} * @return {*}
*/ */
render () { render () {
const { props, state} = this if (!this.props.open) {
if (!props.open) {
return null return null
} }
@ -44,13 +42,13 @@ export default class Menu extends Component {
className: className, className: className,
'data-menu': 'true' 'data-menu': 'true'
}, },
props.items.map(this.renderMenuItem) this.props.items.map(this.renderMenuItem)
) )
} }
renderMenuItem = (item, index) => { renderMenuItem = (item, index) => {
if (item.type === 'separator') { if (item.type === 'separator') {
return h('div', {className: 'jsoneditor-menu-separator'}) return h('div', {key: index, className: 'jsoneditor-menu-separator'})
} }
if (item.click && item.submenu) { if (item.click && item.submenu) {
@ -61,24 +59,24 @@ export default class Menu extends Component {
} }
// two buttons: direct click and a small button to expand the submenu // two buttons: direct click and a small button to expand the submenu
return h('div', {className: 'jsoneditor-menu-item'}, [ return h('div', {key: index, className: 'jsoneditor-menu-item'}, [
h('button', {className: 'jsoneditor-menu-button jsoneditor-menu-default ' + item.className, title: item.title, onClick }, [ h('button', {key: 'default', className: 'jsoneditor-menu-button jsoneditor-menu-default ' + item.className, title: item.title, onClick }, [
h('span', {className: 'jsoneditor-icon'}), h('span', {key: 'icon', className: 'jsoneditor-icon'}),
h('span', {className: 'jsoneditor-text'}, item.text) h('span', {key: 'text', className: 'jsoneditor-text'}, item.text)
]), ]),
h('button', {className: 'jsoneditor-menu-button jsoneditor-menu-expand', onClick: this.createExpandHandler(index) }, [ h('button', {key: 'expand', className: 'jsoneditor-menu-button jsoneditor-menu-expand', onClick: this.createExpandHandler(index) },
h('span', {className: 'jsoneditor-icon jsoneditor-icon-expand'}) h('span', {className: 'jsoneditor-icon jsoneditor-icon-expand'})
]), ),
this.renderSubMenu(item.submenu, index) this.renderSubMenu(item.submenu, index)
]) ])
} }
else if (item.submenu) { else if (item.submenu) {
// button expands the submenu // button expands the submenu
return h('div', {className: 'jsoneditor-menu-item'}, [ return h('div', {key: index, className: 'jsoneditor-menu-item'}, [
h('button', {className: 'jsoneditor-menu-button ' + item.className, title: item.title, onClick: this.createExpandHandler(index) }, [ h('button', {key: 'default', className: 'jsoneditor-menu-button ' + item.className, title: item.title, onClick: this.createExpandHandler(index) }, [
h('span', {className: 'jsoneditor-icon'}), h('span', {key: 'icon', className: 'jsoneditor-icon'}),
h('span', {className: 'jsoneditor-text'}, item.text), h('span', {key: 'text', className: 'jsoneditor-text'}, item.text),
h('span', {className: 'jsoneditor-icon jsoneditor-icon-expand'}), h('span', {key: 'expand', className: 'jsoneditor-icon jsoneditor-icon-expand'}),
]), ]),
this.renderSubMenu(item.submenu, index) this.renderSubMenu(item.submenu, index)
]) ])
@ -91,12 +89,12 @@ export default class Menu extends Component {
} }
// just a button (no submenu) // just a button (no submenu)
return h('div', {className: 'jsoneditor-menu-item'}, [ return h('div', {key: index, className: 'jsoneditor-menu-item'},
h('button', {className: 'jsoneditor-menu-button ' + item.className, title: item.title, onClick }, [ h('button', {className: 'jsoneditor-menu-button ' + item.className, title: item.title, onClick }, [
h('span', {className: 'jsoneditor-icon'}), h('span', {key: 'icon', className: 'jsoneditor-icon'}),
h('span', {className: 'jsoneditor-text'}, item.text) h('span', {key: 'text', className: 'jsoneditor-text'}, item.text)
]), ]),
]) )
} }
} }
@ -108,26 +106,26 @@ export default class Menu extends Component {
const expanded = this.state.expanded === index const expanded = this.state.expanded === index
const collapsing = this.state.collapsing === index const collapsing = this.state.collapsing === index
const contents = submenu.map(item => { const contents = submenu.map((item, index) => {
// FIXME: don't create functions in the render function // FIXME: don't create functions in the render function
const onClick = () => { const onClick = () => {
item.click() item.click()
this.props.onRequestClose() this.props.onRequestClose()
} }
return h('div', {className: 'jsoneditor-menu-item'}, [ return h('div', {key: index, className: 'jsoneditor-menu-item'},
h('button', {className: 'jsoneditor-menu-button ' + item.className, title: item.title, onClick }, [ h('button', {className: 'jsoneditor-menu-button ' + item.className, title: item.title, onClick }, [
h('span', {className: 'jsoneditor-icon'}), h('span', {key: 'icon', className: 'jsoneditor-icon'}),
h('span', {className: 'jsoneditor-text'}, item.text) h('span', {key: 'text', className: 'jsoneditor-text'}, item.text)
]), ]),
]) )
}) })
const className = 'jsoneditor-submenu ' + const className = 'jsoneditor-submenu ' +
(expanded ? ' jsoneditor-expanded' : '') + (expanded ? ' jsoneditor-expanded' : '') +
(collapsing ? ' jsoneditor-collapsing' : '') (collapsing ? ' jsoneditor-collapsing' : '')
return h('div', {className: className}, contents) return h('div', {key: 'submenu', className: className}, contents)
} }
createExpandHandler (index) { createExpandHandler (index) {

View File

@ -21,11 +21,13 @@ export default class ModeButton extends Component {
return h('div', {className: 'jsoneditor-modes'}, [ return h('div', {className: 'jsoneditor-modes'}, [
h('button', { h('button', {
key: 'button',
title: 'Switch mode', title: 'Switch mode',
onClick: this.handleOpen onClick: this.handleOpen
}, `${toCapital(props.mode)} \u25BC`), }, `${toCapital(props.mode)} \u25BC`),
h(ModeMenu, { h(ModeMenu, {
key: 'menu',
...props, ...props,
open: state.open, open: state.open,
onRequestClose: this.handleRequestClose onRequestClose: this.handleRequestClose

View File

@ -14,13 +14,14 @@ export default class ModeMenu extends Component {
if (props.open) { if (props.open) {
const items = props.modes.map(mode => { const items = props.modes.map(mode => {
return h('button', { return h('button', {
key: mode,
title: `Switch to ${mode} mode`, title: `Switch to ${mode} mode`,
className: 'jsoneditor-menu-button jsoneditor-type-modes' + className: 'jsoneditor-menu-button jsoneditor-type-modes' +
((mode === props.mode) ? ' jsoneditor-selected' : ''), ((mode === props.mode) ? ' jsoneditor-selected' : ''),
onClick: () => { onClick: () => {
try { try {
props.onChangeMode(mode)
props.onRequestClose() props.onRequestClose()
props.onChangeMode(mode)
} }
catch (err) { catch (err) {
props.onError(err) props.onError(err)
@ -30,8 +31,7 @@ export default class ModeMenu extends Component {
}) })
return h('div', { return h('div', {
className: 'jsoneditor-actionmenu jsoneditor-modemenu', className: 'jsoneditor-actionmenu jsoneditor-modemenu'
nodemenu: 'true',
}, items) }, items)
} }
else { else {

View File

@ -1,4 +1,4 @@
import { h, Component } from 'preact' import { createElement as h, Component } from 'react'
import '!style!css!less!./Search.less' import '!style!css!less!./Search.less'
@ -11,14 +11,14 @@ export default class Search extends Component {
} }
} }
render (props, state) { render () {
// TODO: show number of search results left from the input box // TODO: show number of search results left from the input box
// TODO: prev/next // TODO: prev/next
// TODO: focus on search results // TODO: focus on search results
// TODO: expand the focused search result if not expanded // TODO: expand the focused search result if not expanded
return h('div', {class: 'jsoneditor-search'}, return h('div', {className: 'jsoneditor-search'},
h('input', {type: 'text', value: state.text, onInput: this.handleChange}) h('input', {type: 'text', value: this.state.text, onInput: this.handleChange})
) )
} }

View File

@ -39,6 +39,18 @@
<div id="container"></div> <div id="container"></div>
<script> <script>
// prevent contentEditable warnings of React
// https://github.com/facebook/draft-js/issues/53#issuecomment-188280259
console.error = (function() {
const error = console.error
return function(exception) {
if ((exception + '').indexOf('Warning: A component is `contentEditable`') != 0) {
error.apply(console, arguments)
}
}
})()
// create the editor // create the editor
const mode = document.getElementById('mode').value const mode = document.getElementById('mode').value
const container = document.getElementById('container') const container = document.getElementById('container')
@ -136,7 +148,6 @@
editor.setMode(mode) editor.setMode(mode)
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,5 +1,5 @@
import { createElement as h, Component } from 'react' import { createElement as h, Component } from 'react'
import { render } from 'react-dom' import { render, unmountComponentAtNode} from 'react-dom'
import CodeMode from './components/CodeMode' import CodeMode from './components/CodeMode'
import TextMode from './components/TextMode' import TextMode from './components/TextMode'
import TreeMode from './components/TreeMode' import TreeMode from './components/TreeMode'
@ -36,7 +36,6 @@ function jsoneditor (container, options = {}) {
_schema: null, _schema: null,
_modes: modes, _modes: modes,
_mode: null, _mode: null,
_element: null,
_component: null _component: null
} }
@ -142,8 +141,7 @@ function jsoneditor (container, options = {}) {
let success = false let success = false
let initialChildCount = editor._container.children.length let initialChildCount = editor._container.children.length
let element let component = null
let component
try { try {
// find the constructor for the selected mode // find the constructor for the selected mode
const constructor = editor._modes[mode] const constructor = editor._modes[mode]
@ -183,7 +181,7 @@ function jsoneditor (container, options = {}) {
// apply JSON schema (if any) // apply JSON schema (if any)
try { try {
element._component.setSchema(editor._schema) component.setSchema(editor._schema)
} }
catch (err) { catch (err) {
handleError(err) handleError(err)
@ -192,7 +190,6 @@ function jsoneditor (container, options = {}) {
// set JSON (this can throw an error) // set JSON (this can throw an error)
const text = editor._component ? editor._component.getText() : '{}' const text = editor._component ? editor._component.getText() : '{}'
component.setText(text) component.setText(text)
element = editor._container.lastChild
// when setText didn't fail, we will reach this point // when setText didn't fail, we will reach this point
success = true success = true
@ -202,13 +199,7 @@ function jsoneditor (container, options = {}) {
} }
finally { finally {
if (success) { if (success) {
// destroy previous component
if (editor._element) {
unrender(container, editor._element)
}
editor._mode = mode editor._mode = mode
editor._element = element
editor._component = component editor._component = component
} }
else { else {
@ -228,7 +219,7 @@ function jsoneditor (container, options = {}) {
* Remove the editor from the DOM and clean up workers * Remove the editor from the DOM and clean up workers
*/ */
editor.destroy = function () { editor.destroy = function () {
unrender(container, editor._element) unmountComponentAtNode(editor._container)
} }
editor.setMode(options && options.mode || 'tree') editor.setMode(options && options.mode || 'tree')
@ -242,13 +233,4 @@ jsoneditor.utils = {
parseJSONPointer parseJSONPointer
} }
/**
* Destroy a rendered preact component
* @param container
* @param root
*/
function unrender (container, root) {
render('', container, root);
}
module.exports = jsoneditor module.exports = jsoneditor