Coder Social home page Coder Social logo

oxyno-zeta / react-editable-json-tree Goto Github PK

View Code? Open in Web Editor NEW
106.0 6.0 18.0 2.45 MB

React Editable Json Tree

Home Page: https://oxyno-zeta.github.io/react-editable-json-tree/

License: MIT License

JavaScript 88.75% HTML 0.20% TypeScript 11.04%
json-tree react editable reactjs

react-editable-json-tree's Introduction

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

Key Description Type Required Default
data Data to be displayed/edited Object | Array True None

rootName

Key Description Type Required Default
rootName Name of the root object string False root

isCollapsed

Key Description Type Required Default
isCollapsed Whether the node is collapsed (for Array/Object/Error) Function False (keyPath, deep) => (deep !== 0)

Function parameters:

Key Description Type Example
keyPath Path to the current node/value string[] ['object'] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
data Data of the current node/value unknown { string: 'test' } for data: { object: { string: 'test' } }

onFullyUpdate

Key Description Type Required Default
onFullyUpdate Callback function called upon each update with the entire new data structure Function False () => {}

Function parameters:

Key Description Type
data Updated data Object | Array (same type as the data prop)

onDeltaUpdate

Key Description Type Required Default
onDeltaUpdate Callback function called upon each update with only the data that has changed Function False () => {}

Function parameters:

Key Description Type
data Delta data Object

Delta data structure:

Key Description Type Example
type Delta type string 'ADD_DELTA_TYPE', 'REMOVE_DELTA_TYPE', or 'UPDATE_DELTA_TYPE'
keyPath Path to the current node/value string[] ['object'] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
key Modified/created/removed key name string None
newValue New value unknown None
oldValue Old value unknown None

readOnly

Key Description Type Required Default
readOnly If 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 | Function False (keyName, data, keyPath, deep, dataType) => false

This function must return a boolean.

Function parameters:

Key Description Type Example
keyName Key name of the current node/value string 'object' for data: { object: { string: 'test' } }
data Data of the current node/value unknown { string: 'test' } for data: { object: { string: 'test' } }
keyPath Path to the current node/value string[] ['object'] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
dataType Data type of the current node/value string 'Object', 'Array', 'Null', 'Undefined', 'Error', 'Number', ...

getStyle

Key Description Type Required Default
getStyle Callback function which should return the CSS style for each node/value Function False (keyName, data, keyPath, deep, dataType) => {...}

Function parameters:

Key Description Type Example
keyName Key name of the current node/value string 'object' for data: { object: { string: 'test' } }
data data of the current node/value unknown { string: 'test' } for data: { object: { string: 'test' } }
keyPath Path to the current node/value string[] ['object'] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
dataType Data type of the current node/value string '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

Key Description Type Required Default
addButtonElement Custom add button element (to confirm adding a new value to an object/array) JSX.Element False <button>+</button>

The library will add an onClick handler to the element.

cancelButtonElement

Key Description Type Required Default
cancelButtonElement Custom cancel button element (to cancel editing a value) JSX.Element False <button>c</button>

The library will add an onClick handler to the element.

editButtonElement

Key Description Type Required Default
editButtonElement Custom edit button element (to finish editing a value) JSX.Element False <button>e</button>

The library will add an onClick handler to the element.

inputElement

Key Description Type Required Default
inputElement Custom text input element (to edit a value) JSX.Element | Function False (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:

Key Description Type Example
usage Usage of the generated input string All values are listed in INPUT_USAGE_TYPES
keyPath Path to the current node/value string[] [] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
key Key of the current node/value string 'object' for data: { object: { string: 'test' } }
value Value of the key unknown { string: 'test' } for data: { object: { string: 'test' } } on 'object' node
dataType Data type of the value string All values are listed in DATA_TYPES

textareaElement

Key Description Type Required Default
textareaElement Custom textarea element (to edit a long value, like functions) JSX.Element | Function False (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:

Key Description Type Example
usage Usage of the generated input string All values are listed in INPUT_USAGE_TYPES
keyPath Path to the current node/value string[] [] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
key Key of the current node/value string 'object' for data: { object: { string: 'test' } }
value Value of the key unknown { string: 'test' } for data: { object: { string: 'test' } } on 'object' node
dataType Data type of the value string All values are listed in DATA_TYPES

minusMenuElement

Key Description Type Required Default
minusMenuElement Custom minus menu element (to remove a value from an object/array) JSX.Element False <span> - </span>

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

plusMenuElement

Key Description Type Required Default
plusMenuElement Custom plus menu element (to begin adding a new value to an object/array) JSX.Element False <span> + </span>

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

beforeRemoveAction

Key Description Type Required Default
beforeRemoveAction Async function called upon the user trying to remove a node/value with the minus menu element Function False (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:

Key Description Type Example
key Key name of the current node/value string 'object' for data: { object: { string: 'test' } }
keyPath Path to the current node/value string[] [] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
oldValue Old value of the key unknown { string: 'test' } for data: { object: { string: 'test' } } on 'object' node

beforeAddAction

Key Description Type Required Default
beforeAddAction Async function called upon the user trying to add a node/value with the add menu element Function False (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:

Key Description Type Example
key Key of the current node/value string 'string' for data: { object: { string: 'test' } }
keyPath Path to the current node/value string[] ['object'] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
newValue New value of the key unknown 'test' for data: { object: { string: 'test' } } on 'string' node

beforeUpdateAction

Key Description Type Required Default
beforeUpdateAction Async function called upon the user trying to edit a node/value Function False (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:

Key Description Type Example
key Key of the current node/value string 'string' for data: { object: { string: 'test' } }
keyPath Path to the current node/value string[] ['object'] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
oldValue Old value of the key unknown 'test' for data: { object: { string: 'test' } } on 'string' node
newValue New value of the key unknown 'update' for data: { object: { string: 'update' } } on 'string' node

logger

Key Description Type Required Default
logger Object used to log errors caught from promises (using only the 'error' key) Object False { error: () => {} }

onSubmitValueParser

Key Description Type Required Default
onSubmitValueParser Function called upon every value addition/update to parse raw string data from inputElements or textareaElements into the correct object types Function False (isEditMode, keyPath, deep, key, rawValue) => nativeParser(rawValue)

Function parameters:

Key Description Type Example
isEditMode Whether the value is being edited on an existing node/value, otherwise it's being newly added boolean True
keyPath Path to the current node/value string[] ['object'] for data: { object: { string: 'test' } }
deep Depth of the current node number 1 for data: { object: { string: 'test' } } on 'object' node
key Key of the current node/value string 'string' for data: { object: { string: 'test' } }
rawValue Raw string value from the inputElement or textareaElement string 'test' for data: { object: { string: 'test' } }

allowFunctionEvaluation

Key Description Type Required Default
allowFunctionEvaluation Allow strings that appear to be Javascript function definitions to be evaluated as Javascript functions boolean False True

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).

react-editable-json-tree's People

Contributors

oxyno-zeta avatar phanabani avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

react-editable-json-tree's Issues

inputElement Improvement Suggestion

Hello again! I've gotten an idea for an improvement to your library.

Currently, you have the inputElement prop which is used to globally define the element used to edit values. I really like how you've implemented getStyle and readOnly, and I thought it would be useful to have similar functionality for inputElement -- pass it as a function which receives the parameters keyName, data, keyPath, deep, dataType and returns an input element. It probably wouldn't be difficult to maintain backward compatibility like this, either.

The reason I think this change would be helpful is that you can have special handlers for specific types of data (such as numbers, dates, and times). Personally, I would like this so that I could display toggle buttons to change boolean values by clicking instead of having to type "true"/"false".

I'd like to hear what you think about this!

Remove custom element generators in favor of another implementation

Based on discussion in #56 (comment). I might need some clarification because I'm not sure I understand everything. The following is my interpretation.

Definitions

Using the following function/component and types:

function CustomInput({inputUsageType, keyPath, depth, name, originalValue, dataType}) {
  switch (dataType) {
    case DataType.Boolean:
      return <input type="checkbox" />
    default:
      return <input type="text" />
  }
}

<JsonTree inputElement={CustomInput} />
type InputElementProps = {
  inputUsageType: InputUsageType;
  keyPath: string[];
  depth: number;
  name: string;
  originalValue: unknown;
  dataType: DataType;
};

Options

Use a render prop

type Props = {
  inputElement: (InputElementProps) => React.Component;
};

function JsonValue({ inputElement }: Props) {
  // ...
  return (
    <div style={styles.container}>
      {inputElement({ inputUsageType, keyPath, depth, name, originalValue, dataType })}
    </div>
  )
}

Pass a component

type Props = {
  inputElement: React.Component<InputElementProps>;
};

function JsonValue({ inputElement: InputElement }: Props) {
  // ...
  return (
    <div style={styles.container}>
      <InputElement {...{ inputUsageType, keyPath, depth, name, originalValue, dataType }} />
    </div>
  )
}

Modernizing this project

I love this project, but I realized I could not get it to work with modern libraries, so I've been working for a few weeks on updating it! It's not completely ready yet, but I wanted to make an issue so that you're aware and can wait for my changes in case you were for any reason thinking of doing this yourself.

In my change/update-all branch, I have:

  • Updated dependencies to their latest versions
  • Removed/replaced a lot of deprecated libraries
  • Added Typescript support
  • Added Prettier support for automatic formatting
  • Rewritten gulp tasks in typescript with gulp's new task export API
  • Updated various babel things
  • Rewritten webpack configuration files in typescript and fixed deprecations in them (this was very difficult and it might not be 100% perfect, but it appears to be working well!)
  • Rewritten the /dev test page in typescript as functional React components

This branch is fully functional and the component seems to be working as intended on current versions of Node and npm. You might opt to pull this branch now so that people on modern setups can begin using/update this component. You should wait until #36 is resolved before doing that though for security reasons.

Additionally, I am rewriting the core components in typescript as functional components that use React hooks, migrating from class components, since this appears to be the preferred way forward for React components. I'm also working on refactoring some things, like using context and reducers to avoid having to pass lots of props down the tree. You can see my work in my change/modernize-core branch.

I'm hoping that this will allow more people to use this project and make it easier for potential updates in the future! I will do a pull request when I'm done so you can check it out for yourself and see if this is something you would like to merge.

Can't resolve 'babel-runtime/core-js/promise'

Thank you for a great lib! Seems there's some conflict with babel 7 or a missing dependency. Any help appreciated.

Module not found: Error: Can't resolve 'babel-runtime/core-js/promise' in '/node_modules/react-editable-json-tree/dist'

Add Github project for v3.0.0

Hey @oxyno-zeta!

In my personal repos, I've used Github projects to manage versions. If you haven't used them, they're essentially kanban boards. I invited you to a project I created named "React Editable JSON Tree v3.0.0", but I need you to add it to this repo since I don't have admin privileges. If you could do that I'd be super grateful! It would help me keep track of the changes I plan to make and my progress on them.

image

Update tree

Ondeltaupdate - results in old value , new value and key modified. What if the same key "test" exists in multiple Json documents ? Is there a way to uniquely find out which one is updated? Also is there an event on selection of a node for update ?

Security concern

Hi there!

I worked with you several years ago under the name Hawkpath to add some features to this project! I've just recently come back to use it again, and I've noticed a security vulnerability. Can you create a security advisory and add me as a collaborator on it so I can discuss this with you? I have a fix prepared, and the security advisory will allow us to work on it privately.

Thanks in advance!

Error when changing values in array

Hello! I love your module, and I've been using it all weekend. I just found out that when I try to directly change a value in an array, I get an error: TypeError: handleUpdateValue(...) is undefined. This originates from line 178 in ./components/JsonValue.js. This causes the value to change (onDeltaUpdate gets called, but not beforeUpdateAction), but the input box doesn't close. I tried it in Firefox on Linux, and Chrome on Windows (using your demo website) and got the same error each time.

One thing that might help is that if there is an object inside an array, I can edit that object's properties without this error occurring. Only values directly inside the array seem to cause it.

Customize inputElement not working...

Dear author,

I feel this libray quite cool and I have successfully override the addButtonElement, editButtonElement, etc. with Material UI IconButtons.

Looks very nice, but when I try to customize the inputElement, it's not working for me.

My code is like below:

inputElement={(usage, keyPath, deep, keyName, data, dataType) => (
            <TextField />
          )}

I am using React, and I simply replaced the default to this element.

The UI part is modified as below:
image
But when I input values and click on the add button, nothing happened. Even no error in console log.

But if I change back to the default inputElement, the add button works without any issue. So it's not the addButtonElement issue but the inputElement issue.

Can you help to take a look at this and let me know if I am doing anything wrong?

Thanks!

CircleCI permission denied error

Hey @oxyno-zeta!

I'm just about ready to release v2.3.0. The last issue I'm working on, #42, ran into a problem that I need your help for.

I've updated the CircleCI config, but the build failed with the reason: "Permission denied (publickey)". I think you need to generate a new GitHub deploy key. This answer here seems to give a simpler summary of what you need to do.

I'm not 100% sure if this is the issue, but I don't have the permissions to investigate further, so I need your help whenever you're available! Hopefully this will fix the problem and I can finish up. :)

Update CI tools to use parcel

Following implementation of #38, the CI config(s) need to be updated to use parcel.

@oxyno-zeta It seems that you've been using CircleCI, but I see a Travis config file in this repo. Can I remove the Travis file and focus solely on Circle, or is there a reason it's still needed?

Use parcel

This will replace webpack and gulp, and should be easier to maintain since it requires less configuration.

Update dependencies

I want the library to work on modern setups without a major version bump. This includes adding compatibility for React 17 and 18.

Don't pass mutable keyPath arrays to callback functions while retaining them in our state

Callback functions like getStyle, readOnly, etc. pass a mutable array keyPath, which is also retained in REJT code. Users can mutate this array and disrupt REJT's internal state. We should clone these data structures before passing them to callbacks.

Options:

I think immer is overkill, and implementing a homemade clone isn't really worth it, so I think lodash is an easy and reliable solution.

Value of Deep

Hi, I have a question about how we define the deep value of a node. Considering the exampledata={x: "a string value", y: {z: "y is an object"}}; I expected x and y to have the same deep. But y, being an object, gets the value deep + 1. Is this a bug, if not could you please help me understand why you chose this design.

Remove CircleCI integration

Following #51, I just need you to remove the CircleCI integration from this repo so we don't see the red X as the CI status! GitHub actions are set up and working now in 2.3.0.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.