Commit f2a90f18a129938987f5855cd073cde4d88c2e18
1 parent
79d58035
Json Schema Form: Add Fieldset, Array and Date fields.
Showing
12 changed files
with
445 additions
and
28 deletions
... | ... | @@ -1331,6 +1331,19 @@ |
1331 | 1331 | } |
1332 | 1332 | } |
1333 | 1333 | }, |
1334 | + "@date-io/core": { | |
1335 | + "version": "1.3.11", | |
1336 | + "resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.3.11.tgz", | |
1337 | + "integrity": "sha512-Yxf2ei0vjU38Fizswr/Uwub5QeRiLOHiTRiHUuTdg+biVB+1EUk+h5szas9SEWA2pZDlSo73F5TPuu+zKqOIBQ==" | |
1338 | + }, | |
1339 | + "@date-io/date-fns": { | |
1340 | + "version": "1.3.11", | |
1341 | + "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-1.3.11.tgz", | |
1342 | + "integrity": "sha512-6Pvk4gwCU4L19XYzDUrro861JCQjZkJQjugxAA+M8wsDTW75A5rmSZGa6g2rQQXfg6ox4B7HBx9p6JYDsSPX0g==", | |
1343 | + "requires": { | |
1344 | + "@date-io/core": "^1.3.11" | |
1345 | + } | |
1346 | + }, | |
1334 | 1347 | "@emotion/hash": { |
1335 | 1348 | "version": "0.7.3", |
1336 | 1349 | "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", |
... | ... | @@ -1366,6 +1379,26 @@ |
1366 | 1379 | "react-transition-group": "^4.3.0" |
1367 | 1380 | } |
1368 | 1381 | }, |
1382 | + "@material-ui/icons": { | |
1383 | + "version": "4.5.1", | |
1384 | + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.5.1.tgz", | |
1385 | + "integrity": "sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==", | |
1386 | + "requires": { | |
1387 | + "@babel/runtime": "^7.4.4" | |
1388 | + } | |
1389 | + }, | |
1390 | + "@material-ui/pickers": { | |
1391 | + "version": "3.2.7", | |
1392 | + "resolved": "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.2.7.tgz", | |
1393 | + "integrity": "sha512-dDi8G8TOXssXZQsGCRM4zoDnWMY4O/vvqVCH4ViIHflvS4ek4v30IlFcSONI5jGzL0dmJhNKso2UEn7qS3iZ3g==", | |
1394 | + "requires": { | |
1395 | + "@babel/runtime": "^7.6.0", | |
1396 | + "@types/styled-jsx": "^2.2.8", | |
1397 | + "clsx": "^1.0.2", | |
1398 | + "react-transition-group": "^4.0.0", | |
1399 | + "rifm": "^0.7.0" | |
1400 | + } | |
1401 | + }, | |
1369 | 1402 | "@material-ui/styles": { |
1370 | 1403 | "version": "4.5.0", |
1371 | 1404 | "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", |
... | ... | @@ -1651,6 +1684,14 @@ |
1651 | 1684 | "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", |
1652 | 1685 | "dev": true |
1653 | 1686 | }, |
1687 | + "@types/styled-jsx": { | |
1688 | + "version": "2.2.8", | |
1689 | + "resolved": "https://registry.npmjs.org/@types/styled-jsx/-/styled-jsx-2.2.8.tgz", | |
1690 | + "integrity": "sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg==", | |
1691 | + "requires": { | |
1692 | + "@types/react": "*" | |
1693 | + } | |
1694 | + }, | |
1654 | 1695 | "@types/tinycolor2": { |
1655 | 1696 | "version": "1.4.2", |
1656 | 1697 | "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", |
... | ... | @@ -3725,6 +3766,11 @@ |
3725 | 3766 | "assert-plus": "^1.0.0" |
3726 | 3767 | } |
3727 | 3768 | }, |
3769 | + "date-fns": { | |
3770 | + "version": "2.5.0", | |
3771 | + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.5.0.tgz", | |
3772 | + "integrity": "sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg==" | |
3773 | + }, | |
3728 | 3774 | "date-format": { |
3729 | 3775 | "version": "2.1.0", |
3730 | 3776 | "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", |
... | ... | @@ -9131,6 +9177,14 @@ |
9131 | 9177 | "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", |
9132 | 9178 | "dev": true |
9133 | 9179 | }, |
9180 | + "rifm": { | |
9181 | + "version": "0.7.0", | |
9182 | + "resolved": "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz", | |
9183 | + "integrity": "sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==", | |
9184 | + "requires": { | |
9185 | + "@babel/runtime": "^7.3.1" | |
9186 | + } | |
9187 | + }, | |
9134 | 9188 | "rimraf": { |
9135 | 9189 | "version": "2.7.1", |
9136 | 9190 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", | ... | ... |
... | ... | @@ -24,8 +24,11 @@ |
24 | 24 | "@angular/platform-browser-dynamic": "~8.2.11", |
25 | 25 | "@angular/router": "~8.2.11", |
26 | 26 | "@auth0/angular-jwt": "^3.0.0", |
27 | + "@date-io/date-fns": "^1.3.11", | |
27 | 28 | "@mat-datetimepicker/core": "^2.0.1", |
28 | 29 | "@material-ui/core": "^4.5.1", |
30 | + "@material-ui/icons": "^4.5.1", | |
31 | + "@material-ui/pickers": "^3.2.7", | |
29 | 32 | "@ngrx/effects": "^8.2.0", |
30 | 33 | "@ngrx/store": "^8.2.0", |
31 | 34 | "@ngrx/store-devtools": "^8.2.0", |
... | ... | @@ -38,6 +41,7 @@ |
38 | 41 | "base64-js": "^1.3.1", |
39 | 42 | "compass-sass-mixins": "^0.12.7", |
40 | 43 | "core-js": "^3.1.4", |
44 | + "date-fns": "^2.5.0", | |
41 | 45 | "deep-equal": "^1.0.1", |
42 | 46 | "flot": "git://github.com/thingsboard/flot.git#0.9-work", |
43 | 47 | "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", | ... | ... |
... | ... | @@ -159,7 +159,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato |
159 | 159 | |
160 | 160 | onFullscreenChanged() {} |
161 | 161 | |
162 | - private onModelChange(key: string | string[], val: any) { | |
162 | + private onModelChange(key: (string | number)[], val: any) { | |
163 | 163 | if (isString(val) && val === '') { |
164 | 164 | val = undefined; |
165 | 165 | } | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +import * as React from 'react'; | |
17 | +import JsonFormUtils from './json-form-utils'; | |
18 | +import ThingsboardBaseComponent from './json-form-base-component'; | |
19 | +import Button from '@material-ui/core/Button'; | |
20 | +import _ from 'lodash'; | |
21 | +import IconButton from '@material-ui/core/IconButton'; | |
22 | +import ClearIcon from '@material-ui/icons/Clear'; | |
23 | +import AddIcon from '@material-ui/icons/Add'; | |
24 | +import Tooltip from '@material-ui/core/Tooltip'; | |
25 | +import { | |
26 | + JsonFormData, | |
27 | + JsonFormFieldProps, | |
28 | + JsonFormFieldState | |
29 | +} from '@shared/components/json-form/react/json-form.models'; | |
30 | + | |
31 | +interface ThingsboardArrayState extends JsonFormFieldState { | |
32 | + model: any[]; | |
33 | + keys: number[]; | |
34 | +} | |
35 | + | |
36 | +class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardArrayState> { | |
37 | + | |
38 | + constructor(props) { | |
39 | + super(props); | |
40 | + this.onAppend = this.onAppend.bind(this); | |
41 | + this.onDelete = this.onDelete.bind(this); | |
42 | + const model = JsonFormUtils.selectOrSet(this.props.form.key, this.props.model) || []; | |
43 | + const keys: number[] = []; | |
44 | + for (let i = 0; i < model.length; i++) { | |
45 | + keys.push(i); | |
46 | + } | |
47 | + this.state = { | |
48 | + model, | |
49 | + keys | |
50 | + }; | |
51 | + } | |
52 | + | |
53 | + componentDidMount() { | |
54 | + if (this.props.form.startEmpty !== true && this.state.model.length === 0) { | |
55 | + this.onAppend(); | |
56 | + } | |
57 | + } | |
58 | + | |
59 | + onAppend() { | |
60 | + let empty; | |
61 | + if (this.props.form && this.props.form.schema && this.props.form.schema.items) { | |
62 | + const items = this.props.form.schema.items; | |
63 | + if (items.type && items.type.indexOf('object') !== -1) { | |
64 | + empty = {}; | |
65 | + if (!this.props.options || this.props.options.setSchemaDefaults !== false) { | |
66 | + empty = typeof items.default !== 'undefined' ? items.default : empty; | |
67 | + if (empty) { | |
68 | + JsonFormUtils.traverseSchema(items, (prop, path) => { | |
69 | + if (typeof prop.default !== 'undefined') { | |
70 | + JsonFormUtils.selectOrSet(path, empty, prop.default); | |
71 | + } | |
72 | + }); | |
73 | + } | |
74 | + } | |
75 | + } else if (items.type && items.type.indexOf('array') !== -1) { | |
76 | + empty = []; | |
77 | + if (!this.props.options || this.props.options.setSchemaDefaults !== false) { | |
78 | + empty = items.default || empty; | |
79 | + } | |
80 | + } else { | |
81 | + if (!this.props.options || this.props.options.setSchemaDefaults !== false) { | |
82 | + empty = items.default || empty; | |
83 | + } | |
84 | + } | |
85 | + } | |
86 | + const newModel = this.state.model; | |
87 | + newModel.push(empty); | |
88 | + const newKeys = this.state.keys; | |
89 | + let key = 0; | |
90 | + if (newKeys.length > 0) { | |
91 | + key = newKeys[newKeys.length - 1] + 1; | |
92 | + } | |
93 | + newKeys.push(key); | |
94 | + this.setState({ | |
95 | + model: newModel, | |
96 | + keys: newKeys | |
97 | + } | |
98 | + ); | |
99 | + this.props.onChangeValidate(this.state.model); | |
100 | + } | |
101 | + | |
102 | + onDelete(index: number) { | |
103 | + const newModel = this.state.model; | |
104 | + newModel.splice(index, 1); | |
105 | + const newKeys = this.state.keys; | |
106 | + newKeys.splice(index, 1); | |
107 | + this.setState( | |
108 | + { | |
109 | + model: newModel, | |
110 | + keys: newKeys | |
111 | + } | |
112 | + ); | |
113 | + this.props.onChangeValidate(this.state.model); | |
114 | + } | |
115 | + | |
116 | + setIndex(index: number) { | |
117 | + return (form: JsonFormData) => { | |
118 | + if (form.key) { | |
119 | + form.key[form.key.indexOf('')] = index; | |
120 | + } | |
121 | + }; | |
122 | + } | |
123 | + | |
124 | + copyWithIndex(form: JsonFormData, index: number): JsonFormData { | |
125 | + const copy: JsonFormData = _.cloneDeep(form); | |
126 | + copy.arrayIndex = index; | |
127 | + JsonFormUtils.traverseForm(copy, this.setIndex(index)); | |
128 | + return copy; | |
129 | + } | |
130 | + | |
131 | + render() { | |
132 | + const arrays = []; | |
133 | + const fields = []; | |
134 | + const model = this.state.model; | |
135 | + const keys = this.state.keys; | |
136 | + const items = this.props.form.items; | |
137 | + for (let i = 0; i < model.length; i++ ) { | |
138 | + let removeButton: JSX.Element = null; | |
139 | + if (!this.props.form.readonly) { | |
140 | + const boundOnDelete = this.onDelete.bind(this, i); | |
141 | + removeButton = <Tooltip title='Remove'><IconButton onClick={boundOnDelete}><ClearIcon/></IconButton></Tooltip>; | |
142 | + } | |
143 | + const forms = (this.props.form.items as JsonFormData[]).map((form, index) => { | |
144 | + const copy = this.copyWithIndex(form, i); | |
145 | + return this.props.builder(copy, this.props.model, index, this.props.onChange, | |
146 | + this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper); | |
147 | + }); | |
148 | + arrays.push( | |
149 | + <li key={keys[i]} className='list-group-item'> | |
150 | + {removeButton} | |
151 | + {forms} | |
152 | + </li> | |
153 | + ); | |
154 | + } | |
155 | + let addButton: JSX.Element = null; | |
156 | + if (!this.props.form.readonly) { | |
157 | + addButton = <Button variant='contained' | |
158 | + color='primary' | |
159 | + startIcon={<AddIcon/>} | |
160 | + onClick={this.onAppend}>{this.props.form.add || 'New'}</Button>; | |
161 | + } | |
162 | + | |
163 | + return ( | |
164 | + <div> | |
165 | + <div className='tb-container'> | |
166 | + <div className='tb-head-label'>{this.props.form.title}</div> | |
167 | + <ol className='list-group'> | |
168 | + {arrays} | |
169 | + </ol> | |
170 | + </div> | |
171 | + {addButton} | |
172 | + </div> | |
173 | + ); | |
174 | + } | |
175 | +} | |
176 | + | |
177 | +export default ThingsboardBaseComponent(ThingsboardArray); | ... | ... |
... | ... | @@ -26,7 +26,7 @@ class ThingsboardCheckbox extends React.Component<JsonFormFieldProps, JsonFormFi |
26 | 26 | <FormControlLabel |
27 | 27 | control={ |
28 | 28 | <Checkbox |
29 | - name={this.props.form.key.slice(-1)[0]} | |
29 | + name={this.props.form.key.slice(-1)[0] + ''} | |
30 | 30 | value={this.props.form.key.slice(-1)[0]} |
31 | 31 | defaultChecked={this.props.value || false} |
32 | 32 | disabled={this.props.form.readonly} | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +import * as React from 'react'; | |
17 | +import ThingsboardBaseComponent from './json-form-base-component'; | |
18 | +import DateFnsUtils from '@date-io/date-fns'; | |
19 | +import { | |
20 | + MuiPickersUtilsProvider, | |
21 | + KeyboardDatePicker | |
22 | +} from '@material-ui/pickers'; | |
23 | +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; | |
24 | + | |
25 | +interface ThingsboardDateState extends JsonFormFieldState { | |
26 | + currentValue: Date | null; | |
27 | +} | |
28 | + | |
29 | +class ThingsboardDate extends React.Component<JsonFormFieldProps, ThingsboardDateState> { | |
30 | + | |
31 | + constructor(props) { | |
32 | + super(props); | |
33 | + this.onDatePicked = this.onDatePicked.bind(this); | |
34 | + let value: Date | null = null; | |
35 | + if (this.props.value && typeof this.props.value === 'number') { | |
36 | + value = new Date(this.props.value); | |
37 | + } | |
38 | + this.state = { | |
39 | + currentValue: value | |
40 | + }; | |
41 | + } | |
42 | + | |
43 | + | |
44 | + onDatePicked(date: Date | null) { | |
45 | + this.setState({ | |
46 | + currentValue: date | |
47 | + }); | |
48 | + this.props.onChangeValidate(date ? date.getTime() : null); | |
49 | + } | |
50 | + | |
51 | + render() { | |
52 | + | |
53 | + let fieldClass = 'tb-date-field'; | |
54 | + if (this.props.form.required) { | |
55 | + fieldClass += ' tb-required'; | |
56 | + } | |
57 | + if (this.props.form.readonly) { | |
58 | + fieldClass += ' tb-readonly'; | |
59 | + } | |
60 | + | |
61 | + return ( | |
62 | + <MuiPickersUtilsProvider utils={DateFnsUtils}> | |
63 | + <div style={{width: '100%', display: 'block'}}> | |
64 | + <KeyboardDatePicker | |
65 | + disableToolbar | |
66 | + variant='inline' | |
67 | + format='MM/dd/yyyy' | |
68 | + margin='normal' | |
69 | + className={fieldClass} | |
70 | + label={this.props.form.title} | |
71 | + value={this.state.currentValue} | |
72 | + onChange={this.onDatePicked} | |
73 | + disabled={this.props.form.readonly} | |
74 | + style={this.props.form.style || {width: '100%'}}/> | |
75 | + | |
76 | + </div> | |
77 | + </MuiPickersUtilsProvider> | |
78 | + ); | |
79 | + } | |
80 | +} | |
81 | + | |
82 | +export default ThingsboardBaseComponent(ThingsboardDate); | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2019 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +import * as React from 'react'; | |
17 | +import { | |
18 | + JsonFormData, | |
19 | + JsonFormFieldProps, | |
20 | + JsonFormFieldState | |
21 | +} from '@shared/components/json-form/react/json-form.models'; | |
22 | + | |
23 | +class ThingsboardFieldSet extends React.Component<JsonFormFieldProps, JsonFormFieldState> { | |
24 | + | |
25 | + render() { | |
26 | + const forms = (this.props.form.items as JsonFormData[]).map((form: JsonFormData, index) => { | |
27 | + return this.props.builder(form, this.props.model, index, this.props.onChange, | |
28 | + this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper); | |
29 | + }); | |
30 | + | |
31 | + return ( | |
32 | + <div style={{paddingTop: '20px'}}> | |
33 | + <div className='tb-head-label'> | |
34 | + {this.props.form.title} | |
35 | + </div> | |
36 | + <div> | |
37 | + {forms} | |
38 | + </div> | |
39 | + </div> | |
40 | + ); | |
41 | + } | |
42 | +} | |
43 | + | |
44 | +export default ThingsboardFieldSet; | ... | ... |
... | ... | @@ -39,7 +39,7 @@ class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, Thingsboar |
39 | 39 | this.onFocus = this.onFocus.bind(this); |
40 | 40 | this.state = { |
41 | 41 | currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'), |
42 | - items: this.props.form.items, | |
42 | + items: this.props.form.items as KeyLabelItem[], | |
43 | 43 | focused: false |
44 | 44 | }; |
45 | 45 | } | ... | ... |
... | ... | @@ -13,8 +13,6 @@ |
13 | 13 | * See the License for the specific language governing permissions and |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | -import './json-form.scss'; | |
17 | - | |
18 | 16 | import * as React from 'react'; |
19 | 17 | import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; |
20 | 18 | import thingsboardTheme from './styles/thingsboardTheme'; | ... | ... |
... | ... | @@ -16,8 +16,8 @@ |
16 | 16 | import * as React from 'react'; |
17 | 17 | import JsonFormUtils from './json-form-utils'; |
18 | 18 | |
19 | -/*import ThingsboardArray from './json-form-array.jsx'; | |
20 | -import ThingsboardJavaScript from './json-form-javascript.jsx'; | |
19 | +import ThingsboardArray from './json-form-array'; | |
20 | +/*import ThingsboardJavaScript from './json-form-javascript.jsx'; | |
21 | 21 | import ThingsboardJson from './json-form-json.jsx'; |
22 | 22 | import ThingsboardHtml from './json-form-html.jsx'; |
23 | 23 | import ThingsboardCss from './json-form-css.jsx'; |
... | ... | @@ -27,11 +27,11 @@ import ThingsboardNumber from './json-form-number'; |
27 | 27 | import ThingsboardText from './json-form-text'; |
28 | 28 | import ThingsboardSelect from './json-form-select'; |
29 | 29 | import ThingsboardRadios from './json-form-radios'; |
30 | -/*import ThingsboardDate from './json-form-date.jsx'; | |
31 | -import ThingsboardImage from './json-form-image.jsx';*/ | |
30 | +import ThingsboardDate from './json-form-date'; | |
31 | +/*import ThingsboardImage from './json-form-image.jsx';*/ | |
32 | 32 | import ThingsboardCheckbox from './json-form-checkbox'; |
33 | 33 | import ThingsboardHelp from './json-form-help'; |
34 | -/*import ThingsboardFieldSet from './json-form-fieldset.jsx';*/ | |
34 | +import ThingsboardFieldSet from './json-form-fieldset'; | |
35 | 35 | import { JsonFormProps, GroupInfo, JsonFormData } from './json-form.models'; |
36 | 36 | |
37 | 37 | import _ from 'lodash'; |
... | ... | @@ -51,18 +51,18 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { |
51 | 51 | textarea: ThingsboardText, |
52 | 52 | select: ThingsboardSelect, |
53 | 53 | radios: ThingsboardRadios, |
54 | - // date: ThingsboardDate, | |
54 | + date: ThingsboardDate, | |
55 | 55 | // image: ThingsboardImage, |
56 | 56 | checkbox: ThingsboardCheckbox, |
57 | 57 | help: ThingsboardHelp, |
58 | - // array: ThingsboardArray, | |
58 | + array: ThingsboardArray, | |
59 | 59 | // javascript: ThingsboardJavaScript, |
60 | 60 | // json: ThingsboardJson, |
61 | 61 | // html: ThingsboardHtml, |
62 | 62 | // css: ThingsboardCss, |
63 | 63 | // color: ThingsboardColor, |
64 | 64 | 'rc-select': ThingsboardRcSelect, |
65 | - // fieldset: ThingsboardFieldSet | |
65 | + fieldset: ThingsboardFieldSet | |
66 | 66 | }; |
67 | 67 | |
68 | 68 | this.onChange = this.onChange.bind(this); |
... | ... | @@ -71,7 +71,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { |
71 | 71 | this.hasConditions = false; |
72 | 72 | } |
73 | 73 | |
74 | - onChange(key: string | string[], val: any) { | |
74 | + onChange(key: (string | number)[], val: any) { | |
75 | 75 | this.props.onModelChange(key, val); |
76 | 76 | if (this.hasConditions) { |
77 | 77 | this.forceUpdate(); |
... | ... | @@ -90,7 +90,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { |
90 | 90 | builder(form: JsonFormData, |
91 | 91 | model: any, |
92 | 92 | index: number, |
93 | - onChange: (key: string | string[], val: any) => void, | |
93 | + onChange: (key: (string | number)[], val: any) => void, | |
94 | 94 | onColorClick: (event: MouseEvent, key: string, val: string) => void, |
95 | 95 | onToggleFullscreen: () => void, |
96 | 96 | mapper: {[type: string]: any}): JSX.Element { | ... | ... |
... | ... | @@ -17,7 +17,13 @@ |
17 | 17 | import * as tv from 'tv4'; |
18 | 18 | import ObjectPath from 'objectpath'; |
19 | 19 | import _ from 'lodash'; |
20 | -import { SchemaValidationResult, DefaultsFormOptions, FormOption } from './json-form.models'; | |
20 | +import { | |
21 | + SchemaValidationResult, | |
22 | + DefaultsFormOptions, | |
23 | + FormOption, | |
24 | + JsonSchemaData, | |
25 | + JsonFormData | |
26 | +} from './json-form.models'; | |
21 | 27 | import { isDefined, isString, isUndefined } from '@core/utils'; |
22 | 28 | import * as equal from 'deep-equal'; |
23 | 29 | |
... | ... | @@ -433,7 +439,7 @@ function merge(schema: any, form: any[], ignore: { [key: string]: boolean }, opt |
433 | 439 | })); |
434 | 440 | } |
435 | 441 | |
436 | -function selectOrSet(projection: string | string[], obj: any, valueToSet?: any): any { | |
442 | +function selectOrSet(projection: string | (string | number)[], obj: any, valueToSet?: any): any { | |
437 | 443 | const numRe = /^\d+$/; |
438 | 444 | |
439 | 445 | if (!obj) { |
... | ... | @@ -474,7 +480,7 @@ function selectOrSet(projection: string | string[], obj: any, valueToSet?: any): |
474 | 480 | return value; |
475 | 481 | } |
476 | 482 | |
477 | -function updateValue(projection: string | string[], obj: any, valueToSet: any): boolean { | |
483 | +function updateValue(projection: string | (string | number)[], obj: any, valueToSet: any): boolean { | |
478 | 484 | const numRe = /^\d+$/; |
479 | 485 | |
480 | 486 | if (!obj) { |
... | ... | @@ -531,12 +537,55 @@ function setValue(obj: any, key: string, val: any): boolean { |
531 | 537 | return changed; |
532 | 538 | } |
533 | 539 | |
540 | +function traverseSchema(schema: JsonSchemaData, fn: (prop: any, path: string[]) => any, path?: string[], ignoreArrays?: boolean) { | |
541 | + ignoreArrays = typeof ignoreArrays !== 'undefined' ? ignoreArrays : true; | |
542 | + | |
543 | + path = path || []; | |
544 | + | |
545 | + const traverse = ($schema: JsonSchemaData, $fn: (prop: any, path: string[]) => any, $path: string[]) => { | |
546 | + $fn($schema, $path); | |
547 | + for (const k of Object.keys($schema.properties)) { | |
548 | + if ($schema.properties.hasOwnProperty(k)) { | |
549 | + const currentPath = $path.slice(); | |
550 | + currentPath.push(k); | |
551 | + traverse($schema.properties[k], $fn, currentPath); | |
552 | + } | |
553 | + } | |
554 | + if (!ignoreArrays && $schema.items) { | |
555 | + const arrPath = $path.slice(); | |
556 | + arrPath.push(''); | |
557 | + traverse($schema.items, $fn, arrPath); | |
558 | + } | |
559 | + }; | |
560 | + | |
561 | + traverse(schema, fn, path || []); | |
562 | +} | |
563 | + | |
564 | +function traverseForm(form: JsonFormData, fn: (form: JsonFormData) => any) { | |
565 | + fn(form); | |
566 | + if (form.items) { | |
567 | + form.items.forEach((f) => { | |
568 | + traverseForm(f, fn); | |
569 | + }); | |
570 | + } | |
571 | + | |
572 | + if (form.tabs) { | |
573 | + form.tabs.forEach((tab) => { | |
574 | + tab.items.forEach((f) => { | |
575 | + traverseForm(f, fn); | |
576 | + }); | |
577 | + }); | |
578 | + } | |
579 | +} | |
580 | + | |
534 | 581 | |
535 | 582 | const utils = { |
536 | 583 | validateBySchema, |
537 | 584 | validate, |
538 | 585 | merge, |
539 | 586 | updateValue, |
540 | - selectOrSet | |
587 | + selectOrSet, | |
588 | + traverseSchema, | |
589 | + traverseForm | |
541 | 590 | }; |
542 | 591 | export default utils; | ... | ... |
... | ... | @@ -55,7 +55,7 @@ export interface JsonFormProps { |
55 | 55 | isFullscreen: boolean; |
56 | 56 | ignore?: {[key: string]: boolean}; |
57 | 57 | option: FormOption; |
58 | - onModelChange?: (key: string | string[], val: any) => void; | |
58 | + onModelChange?: (key: (string | number)[], val: any) => void; | |
59 | 59 | onColorClick?: (event: MouseEvent, key: string, val: string) => void; |
60 | 60 | onToggleFullscreen?: () => void; |
61 | 61 | mapper?: {[type: string]: any}; |
... | ... | @@ -67,9 +67,16 @@ export interface KeyLabelItem { |
67 | 67 | value?: string; |
68 | 68 | } |
69 | 69 | |
70 | +export interface JsonSchemaData { | |
71 | + type: string; | |
72 | + default: any; | |
73 | + items?: JsonSchemaData; | |
74 | + properties?: any; | |
75 | +} | |
76 | + | |
70 | 77 | export interface JsonFormData { |
71 | 78 | type: string; |
72 | - key: string | string[]; | |
79 | + key: (string | number)[]; | |
73 | 80 | title: string; |
74 | 81 | readonly: boolean; |
75 | 82 | required: boolean; |
... | ... | @@ -79,16 +86,15 @@ export interface JsonFormData { |
79 | 86 | rows?: number; |
80 | 87 | rowsMax?: number; |
81 | 88 | placeholder?: string; |
82 | - schema: { | |
83 | - type: string; | |
84 | - default: any; | |
85 | - }; | |
89 | + schema: JsonSchemaData; | |
86 | 90 | titleMap: { |
87 | 91 | value: any; |
88 | 92 | name: string; |
89 | 93 | }[]; |
90 | - items?: Array<KeyLabelItem>; | |
94 | + items?: Array<KeyLabelItem> | Array<JsonFormData>; | |
95 | + tabs?: Array<JsonFormData>; | |
91 | 96 | tags?: any; |
97 | + startEmpty?: boolean; | |
92 | 98 | [key: string]: any; |
93 | 99 | } |
94 | 100 | |
... | ... | @@ -99,17 +105,20 @@ export interface JsonFormFieldProps { |
99 | 105 | builder: (form: JsonFormData, |
100 | 106 | model: any, |
101 | 107 | index: number, |
102 | - onChange: (key: string | string[], val: any) => void, | |
108 | + onChange: (key: (string | number)[], val: any) => void, | |
103 | 109 | onColorClick: (event: MouseEvent, key: string, val: string) => void, |
104 | 110 | onToggleFullscreen: () => void, |
105 | 111 | mapper: {[type: string]: any}) => JSX.Element; |
106 | 112 | mapper?: {[type: string]: any}; |
107 | - onChange?: (key: string | string[], val: any) => void; | |
113 | + onChange?: (key: (string | number)[], val: any) => void; | |
108 | 114 | onColorClick?: (event: MouseEvent, key: string, val: string) => void; |
109 | 115 | onChangeValidate?: (e: any) => void; |
110 | 116 | onToggleFullscreen?: () => void; |
111 | 117 | valid?: boolean; |
112 | 118 | error?: string; |
119 | + options?: { | |
120 | + setSchemaDefaults?: boolean; | |
121 | + }; | |
113 | 122 | } |
114 | 123 | |
115 | 124 | export interface JsonFormFieldState { | ... | ... |