Commit f2a90f18a129938987f5855cd073cde4d88c2e18

Authored by Igor Kulikov
1 parent 79d58035

Json Schema Form: Add Fieldset, Array and Date fields.

@@ -1331,6 +1331,19 @@ @@ -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 "@emotion/hash": { 1347 "@emotion/hash": {
1335 "version": "0.7.3", 1348 "version": "0.7.3",
1336 "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", 1349 "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz",
@@ -1366,6 +1379,26 @@ @@ -1366,6 +1379,26 @@
1366 "react-transition-group": "^4.3.0" 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 "@material-ui/styles": { 1402 "@material-ui/styles": {
1370 "version": "4.5.0", 1403 "version": "4.5.0",
1371 "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", 1404 "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz",
@@ -1651,6 +1684,14 @@ @@ -1651,6 +1684,14 @@
1651 "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", 1684 "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
1652 "dev": true 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 "@types/tinycolor2": { 1695 "@types/tinycolor2": {
1655 "version": "1.4.2", 1696 "version": "1.4.2",
1656 "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", 1697 "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz",
@@ -3725,6 +3766,11 @@ @@ -3725,6 +3766,11 @@
3725 "assert-plus": "^1.0.0" 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 "date-format": { 3774 "date-format": {
3729 "version": "2.1.0", 3775 "version": "2.1.0",
3730 "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", 3776 "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
@@ -9131,6 +9177,14 @@ @@ -9131,6 +9177,14 @@
9131 "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", 9177 "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
9132 "dev": true 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 "rimraf": { 9188 "rimraf": {
9135 "version": "2.7.1", 9189 "version": "2.7.1",
9136 "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 9190 "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -24,8 +24,11 @@ @@ -24,8 +24,11 @@
24 "@angular/platform-browser-dynamic": "~8.2.11", 24 "@angular/platform-browser-dynamic": "~8.2.11",
25 "@angular/router": "~8.2.11", 25 "@angular/router": "~8.2.11",
26 "@auth0/angular-jwt": "^3.0.0", 26 "@auth0/angular-jwt": "^3.0.0",
  27 + "@date-io/date-fns": "^1.3.11",
27 "@mat-datetimepicker/core": "^2.0.1", 28 "@mat-datetimepicker/core": "^2.0.1",
28 "@material-ui/core": "^4.5.1", 29 "@material-ui/core": "^4.5.1",
  30 + "@material-ui/icons": "^4.5.1",
  31 + "@material-ui/pickers": "^3.2.7",
29 "@ngrx/effects": "^8.2.0", 32 "@ngrx/effects": "^8.2.0",
30 "@ngrx/store": "^8.2.0", 33 "@ngrx/store": "^8.2.0",
31 "@ngrx/store-devtools": "^8.2.0", 34 "@ngrx/store-devtools": "^8.2.0",
@@ -38,6 +41,7 @@ @@ -38,6 +41,7 @@
38 "base64-js": "^1.3.1", 41 "base64-js": "^1.3.1",
39 "compass-sass-mixins": "^0.12.7", 42 "compass-sass-mixins": "^0.12.7",
40 "core-js": "^3.1.4", 43 "core-js": "^3.1.4",
  44 + "date-fns": "^2.5.0",
41 "deep-equal": "^1.0.1", 45 "deep-equal": "^1.0.1",
42 "flot": "git://github.com/thingsboard/flot.git#0.9-work", 46 "flot": "git://github.com/thingsboard/flot.git#0.9-work",
43 "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", 47 "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
@@ -159,7 +159,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato @@ -159,7 +159,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
159 159
160 onFullscreenChanged() {} 160 onFullscreenChanged() {}
161 161
162 - private onModelChange(key: string | string[], val: any) { 162 + private onModelChange(key: (string | number)[], val: any) {
163 if (isString(val) && val === '') { 163 if (isString(val) && val === '') {
164 val = undefined; 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,7 +26,7 @@ class ThingsboardCheckbox extends React.Component<JsonFormFieldProps, JsonFormFi
26 <FormControlLabel 26 <FormControlLabel
27 control={ 27 control={
28 <Checkbox 28 <Checkbox
29 - name={this.props.form.key.slice(-1)[0]} 29 + name={this.props.form.key.slice(-1)[0] + ''}
30 value={this.props.form.key.slice(-1)[0]} 30 value={this.props.form.key.slice(-1)[0]}
31 defaultChecked={this.props.value || false} 31 defaultChecked={this.props.value || false}
32 disabled={this.props.form.readonly} 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,7 +39,7 @@ class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, Thingsboar
39 this.onFocus = this.onFocus.bind(this); 39 this.onFocus = this.onFocus.bind(this);
40 this.state = { 40 this.state = {
41 currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'), 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 focused: false 43 focused: false
44 }; 44 };
45 } 45 }
@@ -13,8 +13,6 @@ @@ -13,8 +13,6 @@
13 * See the License for the specific language governing permissions and 13 * See the License for the specific language governing permissions and
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 -import './json-form.scss';  
17 -  
18 import * as React from 'react'; 16 import * as React from 'react';
19 import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; 17 import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
20 import thingsboardTheme from './styles/thingsboardTheme'; 18 import thingsboardTheme from './styles/thingsboardTheme';
@@ -16,8 +16,8 @@ @@ -16,8 +16,8 @@
16 import * as React from 'react'; 16 import * as React from 'react';
17 import JsonFormUtils from './json-form-utils'; 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 import ThingsboardJson from './json-form-json.jsx'; 21 import ThingsboardJson from './json-form-json.jsx';
22 import ThingsboardHtml from './json-form-html.jsx'; 22 import ThingsboardHtml from './json-form-html.jsx';
23 import ThingsboardCss from './json-form-css.jsx'; 23 import ThingsboardCss from './json-form-css.jsx';
@@ -27,11 +27,11 @@ import ThingsboardNumber from './json-form-number'; @@ -27,11 +27,11 @@ import ThingsboardNumber from './json-form-number';
27 import ThingsboardText from './json-form-text'; 27 import ThingsboardText from './json-form-text';
28 import ThingsboardSelect from './json-form-select'; 28 import ThingsboardSelect from './json-form-select';
29 import ThingsboardRadios from './json-form-radios'; 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 import ThingsboardCheckbox from './json-form-checkbox'; 32 import ThingsboardCheckbox from './json-form-checkbox';
33 import ThingsboardHelp from './json-form-help'; 33 import ThingsboardHelp from './json-form-help';
34 -/*import ThingsboardFieldSet from './json-form-fieldset.jsx';*/ 34 +import ThingsboardFieldSet from './json-form-fieldset';
35 import { JsonFormProps, GroupInfo, JsonFormData } from './json-form.models'; 35 import { JsonFormProps, GroupInfo, JsonFormData } from './json-form.models';
36 36
37 import _ from 'lodash'; 37 import _ from 'lodash';
@@ -51,18 +51,18 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { @@ -51,18 +51,18 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
51 textarea: ThingsboardText, 51 textarea: ThingsboardText,
52 select: ThingsboardSelect, 52 select: ThingsboardSelect,
53 radios: ThingsboardRadios, 53 radios: ThingsboardRadios,
54 - // date: ThingsboardDate, 54 + date: ThingsboardDate,
55 // image: ThingsboardImage, 55 // image: ThingsboardImage,
56 checkbox: ThingsboardCheckbox, 56 checkbox: ThingsboardCheckbox,
57 help: ThingsboardHelp, 57 help: ThingsboardHelp,
58 - // array: ThingsboardArray, 58 + array: ThingsboardArray,
59 // javascript: ThingsboardJavaScript, 59 // javascript: ThingsboardJavaScript,
60 // json: ThingsboardJson, 60 // json: ThingsboardJson,
61 // html: ThingsboardHtml, 61 // html: ThingsboardHtml,
62 // css: ThingsboardCss, 62 // css: ThingsboardCss,
63 // color: ThingsboardColor, 63 // color: ThingsboardColor,
64 'rc-select': ThingsboardRcSelect, 64 'rc-select': ThingsboardRcSelect,
65 - // fieldset: ThingsboardFieldSet 65 + fieldset: ThingsboardFieldSet
66 }; 66 };
67 67
68 this.onChange = this.onChange.bind(this); 68 this.onChange = this.onChange.bind(this);
@@ -71,7 +71,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { @@ -71,7 +71,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
71 this.hasConditions = false; 71 this.hasConditions = false;
72 } 72 }
73 73
74 - onChange(key: string | string[], val: any) { 74 + onChange(key: (string | number)[], val: any) {
75 this.props.onModelChange(key, val); 75 this.props.onModelChange(key, val);
76 if (this.hasConditions) { 76 if (this.hasConditions) {
77 this.forceUpdate(); 77 this.forceUpdate();
@@ -90,7 +90,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> { @@ -90,7 +90,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
90 builder(form: JsonFormData, 90 builder(form: JsonFormData,
91 model: any, 91 model: any,
92 index: number, 92 index: number,
93 - onChange: (key: string | string[], val: any) => void, 93 + onChange: (key: (string | number)[], val: any) => void,
94 onColorClick: (event: MouseEvent, key: string, val: string) => void, 94 onColorClick: (event: MouseEvent, key: string, val: string) => void,
95 onToggleFullscreen: () => void, 95 onToggleFullscreen: () => void,
96 mapper: {[type: string]: any}): JSX.Element { 96 mapper: {[type: string]: any}): JSX.Element {
@@ -17,7 +17,13 @@ @@ -17,7 +17,13 @@
17 import * as tv from 'tv4'; 17 import * as tv from 'tv4';
18 import ObjectPath from 'objectpath'; 18 import ObjectPath from 'objectpath';
19 import _ from 'lodash'; 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 import { isDefined, isString, isUndefined } from '@core/utils'; 27 import { isDefined, isString, isUndefined } from '@core/utils';
22 import * as equal from 'deep-equal'; 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,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 const numRe = /^\d+$/; 443 const numRe = /^\d+$/;
438 444
439 if (!obj) { 445 if (!obj) {
@@ -474,7 +480,7 @@ function selectOrSet(projection: string | string[], obj: any, valueToSet?: any): @@ -474,7 +480,7 @@ function selectOrSet(projection: string | string[], obj: any, valueToSet?: any):
474 return value; 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 const numRe = /^\d+$/; 484 const numRe = /^\d+$/;
479 485
480 if (!obj) { 486 if (!obj) {
@@ -531,12 +537,55 @@ function setValue(obj: any, key: string, val: any): boolean { @@ -531,12 +537,55 @@ function setValue(obj: any, key: string, val: any): boolean {
531 return changed; 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 const utils = { 582 const utils = {
536 validateBySchema, 583 validateBySchema,
537 validate, 584 validate,
538 merge, 585 merge,
539 updateValue, 586 updateValue,
540 - selectOrSet 587 + selectOrSet,
  588 + traverseSchema,
  589 + traverseForm
541 }; 590 };
542 export default utils; 591 export default utils;
@@ -55,7 +55,7 @@ export interface JsonFormProps { @@ -55,7 +55,7 @@ export interface JsonFormProps {
55 isFullscreen: boolean; 55 isFullscreen: boolean;
56 ignore?: {[key: string]: boolean}; 56 ignore?: {[key: string]: boolean};
57 option: FormOption; 57 option: FormOption;
58 - onModelChange?: (key: string | string[], val: any) => void; 58 + onModelChange?: (key: (string | number)[], val: any) => void;
59 onColorClick?: (event: MouseEvent, key: string, val: string) => void; 59 onColorClick?: (event: MouseEvent, key: string, val: string) => void;
60 onToggleFullscreen?: () => void; 60 onToggleFullscreen?: () => void;
61 mapper?: {[type: string]: any}; 61 mapper?: {[type: string]: any};
@@ -67,9 +67,16 @@ export interface KeyLabelItem { @@ -67,9 +67,16 @@ export interface KeyLabelItem {
67 value?: string; 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 export interface JsonFormData { 77 export interface JsonFormData {
71 type: string; 78 type: string;
72 - key: string | string[]; 79 + key: (string | number)[];
73 title: string; 80 title: string;
74 readonly: boolean; 81 readonly: boolean;
75 required: boolean; 82 required: boolean;
@@ -79,16 +86,15 @@ export interface JsonFormData { @@ -79,16 +86,15 @@ export interface JsonFormData {
79 rows?: number; 86 rows?: number;
80 rowsMax?: number; 87 rowsMax?: number;
81 placeholder?: string; 88 placeholder?: string;
82 - schema: {  
83 - type: string;  
84 - default: any;  
85 - }; 89 + schema: JsonSchemaData;
86 titleMap: { 90 titleMap: {
87 value: any; 91 value: any;
88 name: string; 92 name: string;
89 }[]; 93 }[];
90 - items?: Array<KeyLabelItem>; 94 + items?: Array<KeyLabelItem> | Array<JsonFormData>;
  95 + tabs?: Array<JsonFormData>;
91 tags?: any; 96 tags?: any;
  97 + startEmpty?: boolean;
92 [key: string]: any; 98 [key: string]: any;
93 } 99 }
94 100
@@ -99,17 +105,20 @@ export interface JsonFormFieldProps { @@ -99,17 +105,20 @@ export interface JsonFormFieldProps {
99 builder: (form: JsonFormData, 105 builder: (form: JsonFormData,
100 model: any, 106 model: any,
101 index: number, 107 index: number,
102 - onChange: (key: string | string[], val: any) => void, 108 + onChange: (key: (string | number)[], val: any) => void,
103 onColorClick: (event: MouseEvent, key: string, val: string) => void, 109 onColorClick: (event: MouseEvent, key: string, val: string) => void,
104 onToggleFullscreen: () => void, 110 onToggleFullscreen: () => void,
105 mapper: {[type: string]: any}) => JSX.Element; 111 mapper: {[type: string]: any}) => JSX.Element;
106 mapper?: {[type: string]: any}; 112 mapper?: {[type: string]: any};
107 - onChange?: (key: string | string[], val: any) => void; 113 + onChange?: (key: (string | number)[], val: any) => void;
108 onColorClick?: (event: MouseEvent, key: string, val: string) => void; 114 onColorClick?: (event: MouseEvent, key: string, val: string) => void;
109 onChangeValidate?: (e: any) => void; 115 onChangeValidate?: (e: any) => void;
110 onToggleFullscreen?: () => void; 116 onToggleFullscreen?: () => void;
111 valid?: boolean; 117 valid?: boolean;
112 error?: string; 118 error?: string;
  119 + options?: {
  120 + setSchemaDefaults?: boolean;
  121 + };
113 } 122 }
114 123
115 export interface JsonFormFieldState { 124 export interface JsonFormFieldState {