2.3.0 • Published 2 years ago

react-editable-json-tree v2.3.0

Weekly downloads
913
License
MIT
Repository
github
Last release
2 years ago

React Editable Json Tree

Node.js CI npm Node version React version

⚠ Security advisory

This library was previously affected by an eval security vulnerability. We have taken steps to mitigate this issue with non-breaking changes in this patch, v2.2.2, but for more info, please read our security advisory.

If you do not have time to read and want to completely mitigate this issue, simply set the allowFunctionEvaluation prop to false. In the next major version, we will set this value to false by default.

Table of Contents

Demo

Demo is available here: Demo

Features

  • Json viewer
  • Collapse node conditionally via callback function
  • Add/remove/update node values
  • Implicit type inference of new values ({} for objects, [] for arrays, true for booleans, etc.)
  • Style via callback function
  • Make entire structure read-only (or individual nodes, by callback function)
  • Callback on global and delta updates
  • Supply custom buttons, inputs, etc. via props
  • Ability to confirm add/remove/update actions

How to use

Install

npm install react-editable-json-tree
# or
yarn add react-editable-json-tree

Example Usage

// Import
import {
    JsonTree,
    ADD_DELTA_TYPE,
    REMOVE_DELTA_TYPE,
    UPDATE_DELTA_TYPE,
    DATA_TYPES,
    INPUT_USAGE_TYPES,
} from 'react-editable-json-tree'

// Data
const data = {
    error: new Error('error'),
    text: 'text',
    int: 100,
    boolean: true,
    null: null,
    object: {
        text: 'text',
        int: 100,
        boolean: true,
    },
    array: [
        1,
        {
            string: 'test',
        },
    ],
}

// Component
<JsonTree data={data} />

Here is a screenshot of the result:

Screenshot

Props

data

KeyDescriptionTypeRequiredDefault
dataData to be displayed/editedObject &#124; ArrayTrueNone

rootName

KeyDescriptionTypeRequiredDefault
rootNameName of the root objectstringFalseroot

isCollapsed

KeyDescriptionTypeRequiredDefault
isCollapsedWhether the node is collapsed (for Array/Object/Error)FunctionFalse(keyPath, deep) => (deep !== 0)

Function parameters:

KeyDescriptionTypeExample
keyPathPath to the current node/valuestring[]['object'] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
dataData of the current node/valueunknown{ string: 'test' } for data: { object: { string: 'test' } }

onFullyUpdate

KeyDescriptionTypeRequiredDefault
onFullyUpdateCallback function called upon each update with the entire new data structureFunctionFalse() => {}

Function parameters:

KeyDescriptionType
dataUpdated dataObject &#124; Array (same type as the data prop)

onDeltaUpdate

KeyDescriptionTypeRequiredDefault
onDeltaUpdateCallback function called upon each update with only the data that has changedFunctionFalse() => {}

Function parameters:

KeyDescriptionType
dataDelta dataObject

Delta data structure:

KeyDescriptionTypeExample
typeDelta typestring'ADD_DELTA_TYPE', 'REMOVE_DELTA_TYPE', or 'UPDATE_DELTA_TYPE'
keyPathPath to the current node/valuestring[]['object'] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
keyModified/created/removed key namestringNone
newValueNew valueunknownNone
oldValueOld valueunknownNone

readOnly

KeyDescriptionTypeRequiredDefault
readOnlyIf a boolean, whether the entire structure should be read-only. If a function, whether the node/value supplied to the function should be read-only (called for all nodes/values).boolean &#124; FunctionFalse(keyName, data, keyPath, deep, dataType) => false

This function must return a boolean.

Function parameters:

KeyDescriptionTypeExample
keyNameKey name of the current node/valuestring'object' for data: { object: { string: 'test' } }
dataData of the current node/valueunknown{ string: 'test' } for data: { object: { string: 'test' } }
keyPathPath to the current node/valuestring[]['object'] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
dataTypeData type of the current node/valuestring'Object', 'Array', 'Null', 'Undefined', 'Error', 'Number', ...

getStyle

KeyDescriptionTypeRequiredDefault
getStyleCallback function which should return the CSS style for each node/valueFunctionFalse(keyName, data, keyPath, deep, dataType) => {...}

Function parameters:

KeyDescriptionTypeExample
keyNameKey name of the current node/valuestring'object' for data: { object: { string: 'test' } }
datadata of the current node/valueunknown{ string: 'test' } for data: { object: { string: 'test' } }
keyPathPath to the current node/valuestring[]['object'] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
dataTypeData type of the current node/valuestring'Object', 'Array', 'Null', 'Undefined', 'Error', 'Number', ...

An example of return:

{
    minus: {
        color: 'red',
    },
    plus: {
        color: 'green',
    },
    collapsed: {
        color: 'grey',
    },
    delimiter: {},
    ul: {
        padding: '0px',
        margin: '0 0 0 25px',
        listStyle: 'none',
    },
    name: {
        color: '#2287CD',
    },
    addForm: {},
}

You can see the default style definitions in src/utils/styles.js.

addButtonElement

KeyDescriptionTypeRequiredDefault
addButtonElementCustom add button element (to confirm adding a new value to an object/array)JSX.ElementFalse<button>+</button>

The library will add an onClick handler to the element.

cancelButtonElement

KeyDescriptionTypeRequiredDefault
cancelButtonElementCustom cancel button element (to cancel editing a value)JSX.ElementFalse<button>c</button>

The library will add an onClick handler to the element.

editButtonElement

KeyDescriptionTypeRequiredDefault
editButtonElementCustom edit button element (to finish editing a value)JSX.ElementFalse<button>e</button>

The library will add an onClick handler to the element.

inputElement

KeyDescriptionTypeRequiredDefault
inputElementCustom text input element (to edit a value)JSX.Element &#124; FunctionFalse(usage, keyPath, deep, keyName, data, dataType) => <input />

The library will add a placeholder, ref, and defaultValue prop to the element. This element will be focused when possible.

Function parameters:

KeyDescriptionTypeExample
usageUsage of the generated inputstringAll values are listed in INPUT_USAGE_TYPES
keyPathPath to the current node/valuestring[][] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
keyKey of the current node/valuestring'object' for data: { object: { string: 'test' } }
valueValue of the keyunknown{ string: 'test' } for data: { object: { string: 'test' } } on 'object' node
dataTypeData type of the valuestringAll values are listed in DATA_TYPES

textareaElement

KeyDescriptionTypeRequiredDefault
textareaElementCustom textarea element (to edit a long value, like functions)JSX.Element &#124; FunctionFalse(usage, keyPath, deep, keyName, data, dataType) => <textarea />

The library will add a ref and defaultValue prop to the element. This element will be focused when possible.

Function parameters:

KeyDescriptionTypeExample
usageUsage of the generated inputstringAll values are listed in INPUT_USAGE_TYPES
keyPathPath to the current node/valuestring[][] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
keyKey of the current node/valuestring'object' for data: { object: { string: 'test' } }
valueValue of the keyunknown{ string: 'test' } for data: { object: { string: 'test' } } on 'object' node
dataTypeData type of the valuestringAll values are listed in DATA_TYPES

minusMenuElement

KeyDescriptionTypeRequiredDefault
minusMenuElementCustom minus menu element (to remove a value from an object/array)JSX.ElementFalse<span> - </span>

The library will add an onClick, className, and style prop to the element.

plusMenuElement

KeyDescriptionTypeRequiredDefault
plusMenuElementCustom plus menu element (to begin adding a new value to an object/array)JSX.ElementFalse<span> + </span>

The library will add an onClick, className, and style prop to the element.

beforeRemoveAction

KeyDescriptionTypeRequiredDefault
beforeRemoveActionAsync function called upon the user trying to remove a node/value with the minus menu elementFunctionFalse(key, keyPath, deep, oldValue) => new Promise(resolve => resolve())

This function must return a Promise. If the promise is resolved, the node/value will be removed. Otherwise, if rejected, nothing will be done.

Function parameters:

KeyDescriptionTypeExample
keyKey name of the current node/valuestring'object' for data: { object: { string: 'test' } }
keyPathPath to the current node/valuestring[][] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
oldValueOld value of the keyunknown{ string: 'test' } for data: { object: { string: 'test' } } on 'object' node

beforeAddAction

KeyDescriptionTypeRequiredDefault
beforeAddActionAsync function called upon the user trying to add a node/value with the add menu elementFunctionFalse(key, keyPath, deep, newValue) => new Promise(resolve => resolve())

This function must return a Promise. If the promise is resolved, the node/value will be added. Otherwise, if rejected, nothing will be done.

Function parameters:

KeyDescriptionTypeExample
keyKey of the current node/valuestring'string' for data: { object: { string: 'test' } }
keyPathPath to the current node/valuestring[]['object'] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
newValueNew value of the keyunknown'test' for data: { object: { string: 'test' } } on 'string' node

beforeUpdateAction

KeyDescriptionTypeRequiredDefault
beforeUpdateActionAsync function called upon the user trying to edit a node/valueFunctionFalse(key, keyPath, deep, oldValue, newValue) => new Promise(resolve => resolve())

This function must return a Promise. If the promise is resolved, the node/value will be updated. Otherwise, if rejected, nothing will be done.

Function parameters:

KeyDescriptionTypeExample
keyKey of the current node/valuestring'string' for data: { object: { string: 'test' } }
keyPathPath to the current node/valuestring[]['object'] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
oldValueOld value of the keyunknown'test' for data: { object: { string: 'test' } } on 'string' node
newValueNew value of the keyunknown'update' for data: { object: { string: 'update' } } on 'string' node

logger

KeyDescriptionTypeRequiredDefault
loggerObject used to log errors caught from promises (using only the 'error' key)ObjectFalse{ error: () => {} }

onSubmitValueParser

KeyDescriptionTypeRequiredDefault
onSubmitValueParserFunction called upon every value addition/update to parse raw string data from inputElements or textareaElements into the correct object typesFunctionFalse(isEditMode, keyPath, deep, key, rawValue) => nativeParser(rawValue)

Function parameters:

KeyDescriptionTypeExample
isEditModeWhether the value is being edited on an existing node/value, otherwise it's being newly addedbooleanTrue
keyPathPath to the current node/valuestring[]['object'] for data: { object: { string: 'test' } }
deepDepth of the current nodenumber1 for data: { object: { string: 'test' } } on 'object' node
keyKey of the current node/valuestring'string' for data: { object: { string: 'test' } }
rawValueRaw string value from the inputElement or textareaElementstring'test' for data: { object: { string: 'test' } }

allowFunctionEvaluation

KeyDescriptionTypeRequiredDefault
allowFunctionEvaluationAllow strings that appear to be Javascript function definitions to be evaluated as Javascript functionsbooleanFalseTrue

Design

The library assigns a CSS class to every element. All classes are prefixed with "rejt" to avoid name clashes. To avoid being linked with a CSS file, the library itself uses inline styles.

Here is the list of CSS classes, ordered by REJT element and depth, with the default HTML element on which each class is applied.

JsonTree

  • rejt-tree (div)

JsonObject

Collapsed

  • rejt-object-node (div)
    • rejt-name (span)
    • rejt-collapsed (span)
      • rejt-collapsed-text (span)
      • rejt-minus-menu (span)

Not Collapsed

  • rejt-object-node (div)
    • rejt-name (span)
    • rejt-not-collapsed (span)
      • rejt-not-collapsed-delimiter (span)
      • rejt-not-collapsed-list (ul)
      • rejt-not-collapsed-delimiter (span)
      • rejt-add-form (span)
      • rejt-plus-menu (span)
      • rejt-minus-menu (span)

JsonArray

Collapsed

  • rejt-array-node (div)
    • rejt-name (span)
    • rejt-collapsed (span)
      • rejt-collapsed-text (span)
      • rejt-minus-menu (span)

Not Collapsed

  • rejt-array-node (div)
    • rejt-name (span)
    • rejt-not-collapsed (span)
      • rejt-not-collapsed-delimiter (span)
      • rejt-not-collapsed-list (ul)
      • rejt-not-collapsed-delimiter (span)
      • rejt-add-form (span)
      • rejt-plus-menu (span)
      • rejt-minus-menu (span)

JsonAddValue

  • rejt-add-value-node (span)

JsonFunctionValue

  • rejt-function-value-node (li)
    • rejt-name (span)
    • rejt-edit-form (span)
    • rejt-value (span)
    • rejt-minus-menu (span)

JsonValue

  • rejt-value-node (li)
    • rejt-name (span)
    • rejt-edit-form (span)
    • rejt-value (span)
    • rejt-minus-menu (span)

Development

npm commands

Build

Build the library to dist/ using parcel.

npm run build

Publish

Publishes the library to npm. This runs a parcel build.

npm publish

Dev app

We have an app available in dev_app/ to test this library in your browser during development. We use yalc to build and link the REJT library inside this subpackage.

If you want to use this dev app, you must run the following command once to initialize yalc:

npm run yalcInit

This will tell yalc to link dev_app/ to the root REJT library (this link is usually stored in ~/.yalc/installations.json if you're curious). After initializing, you can run the following command in the root package every time you make changes to REJT to push the changes to the dev app (with hot-loading!):

npm run yalcPush

You can run the dev app just like any old create-react-app application (make sure you're running this inside the dev_app/ subpackage):

npm start

Inspired by

Thanks

  • My wife BH to support me doing this

Author

Contributors

License

MIT (see License.md).

2.3.0

2 years ago

2.2.2

2 years ago

2.2.1

6 years ago

2.2.0

6 years ago

2.1.0

6 years ago

2.0.0

7 years ago

1.7.0

7 years ago

1.6.0

7 years ago

1.5.0

7 years ago

1.4.0

7 years ago

1.3.1

7 years ago

1.3.0

7 years ago

1.2.0

7 years ago

1.1.0

8 years ago

1.0.1

8 years ago

1.0.0

8 years ago