Skip to content

Commit 087149d

Browse files
committed
add dropdownFactory prop, remove react-select peer dependency
1 parent 6967a87 commit 087149d

8 files changed

Lines changed: 134 additions & 64 deletions

File tree

README.md

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ You must have these packages installed in your project to use this component:
55

66
- `react ^0.14 || ^15.0.0-rc || ^15.0`
77
- `react-dom ^0.14 || ^15.0.0-rc || ^15.0`
8-
- `react-bootstrap ^0.29.3`
9-
- `react-select ^0.7.0`
8+
- `react-bootstrap ^0.29.3` (for tooltips and popovers)
9+
10+
The editor also uses some Font Awesome icons so you should have that as well
1011

1112
## Usage
1213
```js
@@ -16,32 +17,60 @@ import JSONEditor from 'react-json-editor';
1617
<JSONEditor json={{ foo: 'bar' }} onChange={json => console.log(json)} />
1718
```
1819

19-
You'll also need to include the css which is located in `dist/jsonEditor.css'.
20+
You'll also need to include the css which is located in `dist/jsonEditor.css`.
21+
22+
#### Custom dropdowns
23+
The editor uses a dropdown in two places: 1) to select the field type (string, map, bool, etc) and 2) to select the value for a boolean field.
24+
25+
You can pass the `dropdownFactory` prop to render your own dropdown:
26+
27+
```js
28+
<JSONEditor
29+
dropdownFactory={(options, value, onChange, props) => (
30+
<MyOwnDropdown
31+
options={options}
32+
value={value}
33+
onChange={onChange}
34+
{...props}
35+
/>
36+
)}
37+
/>
38+
```
39+
40+
`options` will be an array of objects: `[{ label, value }, { label, value }]`. `label` and `value` will always be strings.
41+
42+
`value` will be the currently selected value, always a string to match the `value` in the `options` object.
43+
44+
`onChange` will be a function that should be called when an option is selected. the function takes a single argument with the value of the selected option
45+
46+
The following additional props may be passed in the `props` argument:
47+
- `placeholder`: placeholder value that can be displayed if the field has no value
2048

21-
### Props
49+
#### Props
2250

23-
|Prop | Type | Default | Description
24-
|----------|------------------|-------------|-------------
25-
|`json` | object or string | `{}` | the json object you want to edit. if you pass a string, the editor will attempt to parse it with `JSON.parse()`.
26-
|`onChange`| function | `noop` | called whenever a change occurs. the updated json is passed as the first and only argument to the function.
51+
|Prop | Type | Default | Description
52+
|------------------|------------------|-------------|-------------
53+
|`json` | object or string | `{}` | the json object you want to edit. if you pass a string, the editor will attempt to parse it with `JSON.parse()`.
54+
|`onChange` | function | `noop` | called whenever a change occurs: `function(updatedJson) {}`
55+
|`dropdownFactory` | function | | factory to render a custom dropdown element for selecting the field type and value if type is boolean: `function(options = [], value, props={}) {}`
2756

2857
## Development
2958

30-
### Building
59+
#### Building
3160
`npm run build` will run the js through babel and the scss through node-sass and output into `dist/`.
3261

33-
The `dist` folder should be committed to the repo.
62+
The `dist` folder should be committed to the repo for now.
3463

35-
### Linting
64+
#### Linting
3665
`npm run lint` - please lint before committing any code
3766

3867
## Known Issues
3968
- Typing a decimal as the last character in a number field doesn't work because of the way we coerce the type - if the field is a number type, we try to `parseFloat()` it, which parses a value like `1.` to `1`. Adding the decimal anywhere but the last character will work, so a workaround to trying to type `1.1` would be to type `11`, then insert the decimal between the `1`s.
4069

4170
## TODO
4271
- Remove react-bootstrap dependency (used for tooltips/popovers)
43-
- Remove react-select dependency (used for dropdown)
44-
- Allow passing input & select factories instead of using bootstrap/react-select components
72+
- Allow passing an input factory or custom classname instead of using bootstrap components
73+
- Remove font awesome dependency
4574
- Add AMD/UMD module support via webpack
4675
- Tests
4776
- Examples

dist/components/Editor.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", {
44
value: true
55
});
66

7+
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
8+
79
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
810

911
var _react = require('react');
@@ -59,6 +61,7 @@ var JSONEditor = function (_Component) {
5961
_this.addArrayElement = _this.addArrayElement.bind(_this);
6062
_this.addMapElement = _this.addMapElement.bind(_this);
6163
_this.removeElement = _this.removeElement.bind(_this);
64+
_this.createDropdown = _this.createDropdown.bind(_this);
6265
_this.undo = _this.undo.bind(_this);
6366
_this.redo = _this.redo.bind(_this);
6467
return _this;
@@ -72,7 +75,8 @@ var JSONEditor = function (_Component) {
7275
onFieldValueChange: this.onFieldValueChange,
7376
addArrayElement: this.addArrayElement,
7477
addMapElement: this.addMapElement,
75-
removeElement: this.removeElement
78+
removeElement: this.removeElement,
79+
createDropdown: this.createDropdown
7680
}
7781
};
7882
}
@@ -164,6 +168,39 @@ var JSONEditor = function (_Component) {
164168
this.redoStack = [];
165169
this.props.onChange(json);
166170
}
171+
}, {
172+
key: 'createDropdown',
173+
value: function createDropdown() {
174+
var options = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
175+
var value = arguments[1];
176+
var _onChange = arguments[2];
177+
var props = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
178+
var dropdownFactory = this.props.dropdownFactory;
179+
180+
181+
if (dropdownFactory && typeof dropdownFactory === 'function') {
182+
return dropdownFactory(options, value, _onChange, props);
183+
}
184+
185+
return React.createElement(
186+
'select',
187+
_extends({ value: value, onChange: function onChange(evt) {
188+
return _onChange(evt.target.value);
189+
}, defaultValue: '' }, props),
190+
React.createElement(
191+
'option',
192+
{ disabled: true, value: '' },
193+
props.placeholder
194+
),
195+
options.map(function (opt, idx) {
196+
return React.createElement(
197+
'option',
198+
{ value: opt.value, key: idx },
199+
opt.label
200+
);
201+
})
202+
);
203+
}
167204
}, {
168205
key: 'undo',
169206
value: function undo() {
@@ -233,7 +270,8 @@ var JSONEditor = function (_Component) {
233270

234271
JSONEditor.propTypes = {
235272
json: _react.PropTypes.oneOfType([_react.PropTypes.object, _react.PropTypes.string]),
236-
onChange: _react.PropTypes.func
273+
onChange: _react.PropTypes.func,
274+
dropdownFactory: _react.PropTypes.func
237275
};
238276
JSONEditor.defaultProps = {
239277
onChange: function onChange() {}

dist/components/Field.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ var _classnames = require('classnames');
1212

1313
var _classnames2 = _interopRequireDefault(_classnames);
1414

15-
var _reactSelect = require('react-select');
16-
17-
var _reactSelect2 = _interopRequireDefault(_reactSelect);
18-
1915
var _OverlayTrigger = require('react-bootstrap/lib/OverlayTrigger');
2016

2117
var _OverlayTrigger2 = _interopRequireDefault(_OverlayTrigger);
@@ -183,18 +179,14 @@ var JSONField = function (_Component) {
183179
key: 'renderTypeSelectorAndButtons',
184180
value: function renderTypeSelectorAndButtons() {
185181
var fieldValue = this.props.fieldValue;
182+
var createDropdown = this.context.jsonEditor.createDropdown;
186183

187184

188185
return React.createElement(
189186
'span',
190187
null,
191-
React.createElement(_reactSelect2.default, {
192-
options: typeSelectorOptions,
193-
clearable: false,
194-
searchable: false,
195-
value: (0, _lib.getValueType)(fieldValue),
196-
placeholder: 'Type',
197-
onChange: this.onTypeChange
188+
createDropdown(typeSelectorOptions, (0, _lib.getValueType)(fieldValue), this.onTypeChange, {
189+
placeholder: 'Type'
198190
}),
199191
fieldValue !== null && React.createElement(
200192
_OverlayTrigger2.default,

dist/components/ValueEditor.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,16 @@ exports.default = undefined;
77

88
var _react = require('react');
99

10-
var _reactSelect = require('react-select');
11-
12-
var _reactSelect2 = _interopRequireDefault(_reactSelect);
13-
1410
var _lib = require('../lib');
1511

16-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17-
1812
var boolOptions = [{
1913
value: 'true', label: 'True'
2014
}, {
2115
value: 'false', label: 'False'
2216
}];
2317

24-
// ReactSelect doesn't like boolean values :-\
18+
// values in the bool options are strings for ease of use across dropdown components
19+
// convert the value back to an actual boolean when updating the json value
2520
function convertToActualBool(string) {
2621
return string === 'true';
2722
}
@@ -43,24 +38,21 @@ function validateKeyPress(evt, fieldValue, valueType) {
4338
}
4439
}
4540

46-
function ValueEditor(props) {
41+
function ValueEditor(props, context) {
4742
var _onChange = props.onChange;
4843
var fieldValue = props.fieldValue;
44+
var createDropdown = context.jsonEditor.createDropdown;
4945

5046
var valueType = (0, _lib.getValueType)(fieldValue);
5147

5248
if (valueType === 'boolean') {
5349
return React.createElement(
5450
'span',
5551
{ className: 'bool-selector' },
56-
React.createElement(_reactSelect2.default, {
57-
value: String(fieldValue),
58-
options: boolOptions,
59-
clearable: false,
60-
searchable: false,
61-
onChange: function onChange(val) {
62-
return _onChange(convertToActualBool(val));
63-
}
52+
createDropdown(boolOptions, String(fieldValue), function (val) {
53+
return _onChange(convertToActualBool(val));
54+
}, {
55+
placeholder: 'Value'
6456
})
6557
);
6658
}
@@ -82,4 +74,8 @@ exports.default = ValueEditor;
8274
ValueEditor.propTypes = {
8375
fieldValue: _react.PropTypes.oneOfType([_react.PropTypes.string, _react.PropTypes.array, _react.PropTypes.object, _react.PropTypes.number, _react.PropTypes.bool]),
8476
onChange: _react.PropTypes.func
77+
};
78+
79+
ValueEditor.contextTypes = {
80+
jsonEditor: _react.PropTypes.object
8581
};

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-json-editor",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "A JSON editor for ReactJS",
55
"main": "dist/index.js",
66
"scripts": {
@@ -30,8 +30,7 @@
3030
"peerDependencies": {
3131
"react": "^0.14 || ^15.0.0-rc || ^15.0",
3232
"react-dom": "^0.14 || ^15.0.0-rc || ^15.0",
33-
"react-bootstrap": "^0.29.3",
34-
"react-select": "^0.7.0"
33+
"react-bootstrap": "^0.29.3"
3534
},
3635
"dependencies": {
3736
"classnames": "^2.2.5",

src/components/Editor.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default class JSONEditor extends Component {
1515
PropTypes.string,
1616
]),
1717
onChange: PropTypes.func,
18+
dropdownFactory: PropTypes.func,
1819
}
1920

2021
static defaultProps = {
@@ -35,6 +36,7 @@ export default class JSONEditor extends Component {
3536
this.addArrayElement = ::this.addArrayElement;
3637
this.addMapElement = ::this.addMapElement;
3738
this.removeElement = ::this.removeElement;
39+
this.createDropdown = ::this.createDropdown;
3840
this.undo = ::this.undo;
3941
this.redo = ::this.redo;
4042
}
@@ -46,6 +48,7 @@ export default class JSONEditor extends Component {
4648
addArrayElement: this.addArrayElement,
4749
addMapElement: this.addMapElement,
4850
removeElement: this.removeElement,
51+
createDropdown: this.createDropdown,
4952
},
5053
};
5154
}
@@ -132,6 +135,23 @@ export default class JSONEditor extends Component {
132135
this.props.onChange(json);
133136
}
134137

138+
createDropdown(options = [], value, onChange, props = {}) {
139+
const { dropdownFactory } = this.props;
140+
141+
if (dropdownFactory && typeof dropdownFactory === 'function') {
142+
return dropdownFactory(options, value, onChange, props);
143+
}
144+
145+
return (
146+
<select value={value} onChange={evt => onChange(evt.target.value)} defaultValue="" {...props}>
147+
<option disabled={true} value="">{ props.placeholder }</option>
148+
{ options.map((opt, idx) =>
149+
<option value={opt.value} key={idx}>{ opt.label }</option>
150+
)}
151+
</select>
152+
);
153+
}
154+
135155
undo() {
136156
const lastAction = this.undoStack.pop();
137157
const json = cloneDeep(this.parseJson());

src/components/Field.js

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Component, PropTypes } from 'react';
22
import cn from 'classnames';
3-
import ReactSelect from 'react-select';
43
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
54
import Tooltip from 'react-bootstrap/lib/Tooltip';
65

@@ -141,17 +140,13 @@ export default class JSONField extends Component {
141140

142141
renderTypeSelectorAndButtons() {
143142
const { fieldValue } = this.props;
143+
const { createDropdown } = this.context.jsonEditor;
144144

145145
return (
146146
<span>
147-
<ReactSelect
148-
options={typeSelectorOptions}
149-
clearable={false}
150-
searchable={false}
151-
value={getValueType(fieldValue)}
152-
placeholder="Type"
153-
onChange={this.onTypeChange}
154-
/>
147+
{ createDropdown(typeSelectorOptions, getValueType(fieldValue), this.onTypeChange, {
148+
placeholder: 'Type',
149+
})}
155150

156151
{ fieldValue !== null &&
157152
<OverlayTrigger overlay={<Tooltip id="nullify">Make null</Tooltip>} placement="left">

0 commit comments

Comments
 (0)